Animates a fake TaskView in Home gesture tutorial.
The "TaskView" morphs as you drag up from the nav bar, and transforms into a "home icon" or final "TaskView" when you release your finger. If you cancel the gesture (e.g. drag back down), the view simply fades out and provides a hint to swipe straight up. Demo: https://drive.google.com/open?id=1NXlO1W6IhLoX0k2K6b-QZ5IXp0o7OYqO Bug: 148542211 Change-Id: I3d5e9a45fcadc6a498941ea17813b9487720a504
This commit is contained in:
parent
625bd51fe3
commit
f8a8117676
|
@ -15,58 +15,38 @@
|
|||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
|
||||
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Matrix.ScaleToFit;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.testing.TestProtocol;
|
||||
import com.android.launcher3.touch.PagedOrientationHandler;
|
||||
import com.android.launcher3.util.VibratorWrapper;
|
||||
import com.android.launcher3.util.WindowBounds;
|
||||
import com.android.launcher3.views.FloatingIconView;
|
||||
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
|
||||
import com.android.quickstep.util.ActiveGestureLog;
|
||||
import com.android.quickstep.util.ActivityInitListener;
|
||||
import com.android.quickstep.util.RectFSpringAnim;
|
||||
import com.android.quickstep.util.TaskViewSimulator;
|
||||
import com.android.quickstep.util.TransformParams;
|
||||
import com.android.quickstep.util.TransformParams.BuilderProxy;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.InputConsumerController;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
|
||||
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
|
@ -76,40 +56,13 @@ import java.util.function.Consumer;
|
|||
*/
|
||||
@TargetApi(Build.VERSION_CODES.Q)
|
||||
public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
|
||||
implements RecentsAnimationListener {
|
||||
extends SwipeUpAnimationLogic implements RecentsAnimationListener {
|
||||
|
||||
private static final String TAG = "BaseSwipeUpHandler";
|
||||
protected static final Rect TEMP_RECT = new Rect();
|
||||
|
||||
public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
|
||||
private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
|
||||
|
||||
// The distance needed to drag to reach the task size in recents.
|
||||
protected int mTransitionDragLength;
|
||||
// How much further we can drag past recents, as a factor of mTransitionDragLength.
|
||||
protected float mDragLengthFactor = 1;
|
||||
// Start resisting when swiping past this factor of mTransitionDragLength.
|
||||
private float mDragLengthFactorStartPullback = 1f;
|
||||
// This is how far down we can scale down, where 0f is full screen and 1f is recents.
|
||||
private float mDragLengthFactorMaxPullback = 1f;
|
||||
|
||||
protected final Context mContext;
|
||||
protected final RecentsAnimationDeviceState mDeviceState;
|
||||
protected final GestureState mGestureState;
|
||||
protected final BaseActivityInterface<?, T> mActivityInterface;
|
||||
protected final InputConsumerController mInputConsumer;
|
||||
|
||||
protected final TaskViewSimulator mTaskViewSimulator;
|
||||
private AnimatorPlaybackController mWindowTransitionController;
|
||||
|
||||
protected final TransformParams mTransformParams = new TransformParams();
|
||||
|
||||
// Shift in the range of [0, 1].
|
||||
// 0 => preview snapShot is completely visible, and hotseat is completely translated down
|
||||
// 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
|
||||
// visible.
|
||||
protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
|
||||
|
||||
protected final ActivityInitListener mActivityInitListener;
|
||||
|
||||
protected RecentsAnimationController mRecentsAnimationController;
|
||||
|
@ -120,7 +73,6 @@ public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extend
|
|||
|
||||
protected T mActivity;
|
||||
protected Q mRecentsView;
|
||||
protected DeviceProfile mDp;
|
||||
|
||||
protected Runnable mGestureEndCallback;
|
||||
|
||||
|
@ -132,13 +84,10 @@ public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extend
|
|||
|
||||
protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
|
||||
GestureState gestureState, InputConsumerController inputConsumer) {
|
||||
mContext = context;
|
||||
mDeviceState = deviceState;
|
||||
mGestureState = gestureState;
|
||||
super(context, deviceState, gestureState, new TransformParams());
|
||||
mActivityInterface = gestureState.getActivityInterface();
|
||||
mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
|
||||
mInputConsumer = inputConsumer;
|
||||
mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -158,28 +107,6 @@ public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extend
|
|||
return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void updateDisplacement(float displacement) {
|
||||
// We are moving in the negative x/y direction
|
||||
displacement = -displacement;
|
||||
float shift;
|
||||
if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
|
||||
shift = mDragLengthFactor;
|
||||
} else {
|
||||
float translation = Math.max(displacement, 0);
|
||||
shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
|
||||
if (shift > mDragLengthFactorStartPullback) {
|
||||
float pullbackProgress = Utilities.getProgress(shift,
|
||||
mDragLengthFactorStartPullback, mDragLengthFactor);
|
||||
pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
|
||||
shift = mDragLengthFactorStartPullback + pullbackProgress
|
||||
* (mDragLengthFactorMaxPullback - mDragLengthFactorStartPullback);
|
||||
}
|
||||
}
|
||||
|
||||
mCurrentShift.updateValue(shift);
|
||||
}
|
||||
|
||||
public void setGestureEndCallback(Runnable gestureEndCallback) {
|
||||
mGestureEndCallback = gestureEndCallback;
|
||||
}
|
||||
|
@ -276,6 +203,7 @@ public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extend
|
|||
RecentsAnimationTargets targets) {
|
||||
mRecentsAnimationController = recentsAnimationController;
|
||||
mRecentsAnimationTargets = targets;
|
||||
mTransformParams.setTargetSet(mRecentsAnimationTargets);
|
||||
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
|
||||
RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
|
||||
mGestureState.getRunningTaskId());
|
||||
|
@ -357,35 +285,6 @@ public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extend
|
|||
return mGestureState.getLastStartedTaskId() != -1;
|
||||
}
|
||||
|
||||
protected void initTransitionEndpoints(DeviceProfile dp) {
|
||||
mDp = dp;
|
||||
|
||||
mTaskViewSimulator.setDp(dp);
|
||||
mTaskViewSimulator.setLayoutRotation(
|
||||
mDeviceState.getCurrentActiveRotation(),
|
||||
mDeviceState.getDisplayRotation());
|
||||
mTransitionDragLength = mActivityInterface.getSwipeUpDestinationAndLength(
|
||||
dp, mContext, TEMP_RECT,
|
||||
mTaskViewSimulator.getOrientationState().getOrientationHandler());
|
||||
|
||||
if (mDeviceState.isFullyGesturalNavMode()) {
|
||||
// We can drag all the way to the top of the screen.
|
||||
mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
|
||||
|
||||
float startScale = mTaskViewSimulator.getFullScreenScale();
|
||||
// Start pulling back when RecentsView scale is 0.75f, and let it go down to 0.5f.
|
||||
mDragLengthFactorStartPullback = (0.75f - startScale) / (1 - startScale);
|
||||
mDragLengthFactorMaxPullback = (0.5f - startScale) / (1 - startScale);
|
||||
} else {
|
||||
mDragLengthFactor = 1;
|
||||
mDragLengthFactorStartPullback = mDragLengthFactorMaxPullback = 1;
|
||||
}
|
||||
|
||||
PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2);
|
||||
mTaskViewSimulator.addAppToOverviewAnim(pa, t -> t * mDragLengthFactor);
|
||||
mWindowTransitionController = pa.createPlaybackController();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the window should be translated horizontally if the recents view scrolls
|
||||
*/
|
||||
|
@ -457,7 +356,6 @@ public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extend
|
|||
if (mWindowTransitionController != null) {
|
||||
float progress = mCurrentShift.value / mDragLengthFactor;
|
||||
mWindowTransitionController.setPlayFraction(progress);
|
||||
mTransformParams.setTargetSet(mRecentsAnimationTargets);
|
||||
|
||||
if (mRecentsViewScrollLinked) {
|
||||
mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset());
|
||||
|
@ -466,217 +364,9 @@ public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extend
|
|||
}
|
||||
}
|
||||
|
||||
protected PagedOrientationHandler getOrientationHandler() {
|
||||
return mTaskViewSimulator.getOrientationState().getOrientationHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an animation that transforms the current app window into the home app.
|
||||
* @param startProgress The progress of {@link #mCurrentShift} to start the window from.
|
||||
* @param homeAnimationFactory The home animation factory.
|
||||
*/
|
||||
protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
|
||||
HomeAnimationFactory homeAnimationFactory) {
|
||||
final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
|
||||
final FloatingIconView fiv = homeAnimationFactory.mIconView;
|
||||
final boolean isFloatingIconView = fiv != null;
|
||||
|
||||
mWindowTransitionController.setPlayFraction(startProgress / mDragLengthFactor);
|
||||
mTaskViewSimulator.apply(mTransformParams
|
||||
.setProgress(startProgress)
|
||||
.setTargetSet(mRecentsAnimationTargets));
|
||||
RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
|
||||
|
||||
// Matrix to map a rect in Launcher space to window space
|
||||
Matrix homeToWindowPositionMap = new Matrix();
|
||||
mTaskViewSimulator.applyWindowToHomeRotation(homeToWindowPositionMap);
|
||||
|
||||
final RectF startRect = new RectF(cropRectF);
|
||||
mTaskViewSimulator.getCurrentMatrix().mapRect(startRect);
|
||||
// Move the startRect to Launcher space as floatingIconView runs in Launcher
|
||||
Matrix windowToHomePositionMap = new Matrix();
|
||||
homeToWindowPositionMap.invert(windowToHomePositionMap);
|
||||
windowToHomePositionMap.mapRect(startRect);
|
||||
|
||||
RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext);
|
||||
if (isFloatingIconView) {
|
||||
anim.addAnimatorListener(fiv);
|
||||
fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
|
||||
fiv.setFastFinishRunnable(anim::end);
|
||||
}
|
||||
|
||||
SpringAnimationRunner runner = new SpringAnimationRunner(
|
||||
homeAnimationFactory, cropRectF, homeToWindowPositionMap);
|
||||
anim.addOnUpdateListener(runner);
|
||||
anim.addAnimatorListener(runner);
|
||||
return anim;
|
||||
}
|
||||
|
||||
public interface Factory {
|
||||
|
||||
BaseSwipeUpHandler newHandler(
|
||||
GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
|
||||
}
|
||||
|
||||
protected interface RunningWindowAnim {
|
||||
void end();
|
||||
|
||||
void cancel();
|
||||
|
||||
static RunningWindowAnim wrap(Animator animator) {
|
||||
return new RunningWindowAnim() {
|
||||
@Override
|
||||
public void end() {
|
||||
animator.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
animator.cancel();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
|
||||
return new RunningWindowAnim() {
|
||||
@Override
|
||||
public void end() {
|
||||
rectFSpringAnim.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
rectFSpringAnim.cancel();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param progress The progress of the animation to the home screen.
|
||||
* @return The current alpha to set on the animating app window.
|
||||
*/
|
||||
protected float getWindowAlpha(float progress) {
|
||||
// Alpha interpolates between [1, 0] between progress values [start, end]
|
||||
final float start = 0f;
|
||||
final float end = 0.85f;
|
||||
|
||||
if (progress <= start) {
|
||||
return 1f;
|
||||
}
|
||||
if (progress >= end) {
|
||||
return 0f;
|
||||
}
|
||||
return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
|
||||
}
|
||||
|
||||
protected abstract class HomeAnimationFactory {
|
||||
|
||||
private FloatingIconView mIconView;
|
||||
|
||||
public HomeAnimationFactory(@Nullable FloatingIconView iconView) {
|
||||
mIconView = iconView;
|
||||
}
|
||||
|
||||
public @NonNull RectF getWindowTargetRect() {
|
||||
PagedOrientationHandler orientationHandler = getOrientationHandler();
|
||||
DeviceProfile dp = mDp;
|
||||
final int halfIconSize = dp.iconSizePx / 2;
|
||||
float primaryDimension = orientationHandler
|
||||
.getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx);
|
||||
float secondaryDimension = orientationHandler
|
||||
.getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx);
|
||||
final float targetX = primaryDimension / 2f;
|
||||
final float targetY = secondaryDimension - dp.hotseatBarSizePx;
|
||||
// Fallback to animate to center of screen.
|
||||
return new RectF(targetX - halfIconSize, targetY - halfIconSize,
|
||||
targetX + halfIconSize, targetY + halfIconSize);
|
||||
}
|
||||
|
||||
public abstract @NonNull AnimatorPlaybackController createActivityAnimationToHome();
|
||||
|
||||
public void playAtomicAnimation(float velocity) {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
|
||||
private class SpringAnimationRunner extends AnimationSuccessListener
|
||||
implements RectFSpringAnim.OnUpdateListener, BuilderProxy {
|
||||
|
||||
final Rect mCropRect = new Rect();
|
||||
final Matrix mMatrix = new Matrix();
|
||||
|
||||
final RectF mWindowCurrentRect = new RectF();
|
||||
final Matrix mHomeToWindowPositionMap;
|
||||
|
||||
final FloatingIconView mFIV;
|
||||
final AnimatorPlaybackController mHomeAnim;
|
||||
final RectF mCropRectF;
|
||||
|
||||
final float mStartRadius;
|
||||
final float mEndRadius;
|
||||
final float mWindowAlphaThreshold;
|
||||
|
||||
SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
|
||||
Matrix homeToWindowPositionMap) {
|
||||
mHomeAnim = factory.createActivityAnimationToHome();
|
||||
mCropRectF = cropRectF;
|
||||
mHomeToWindowPositionMap = homeToWindowPositionMap;
|
||||
|
||||
cropRectF.roundOut(mCropRect);
|
||||
mFIV = factory.mIconView;
|
||||
|
||||
// End on a "round-enough" radius so that the shape reveal doesn't have to do too much
|
||||
// rounding at the end of the animation.
|
||||
mStartRadius = mTaskViewSimulator.getCurrentCornerRadius();
|
||||
mEndRadius = cropRectF.width() / 2f;
|
||||
|
||||
// We want the window alpha to be 0 once this threshold is met, so that the
|
||||
// FolderIconView can be seen morphing into the icon shape.
|
||||
mWindowAlphaThreshold = mFIV != null ? 1f - SHAPE_PROGRESS_DURATION : 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(RectF currentRect, float progress) {
|
||||
mHomeAnim.setPlayFraction(progress);
|
||||
mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
|
||||
|
||||
mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
|
||||
float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
|
||||
mTransformParams
|
||||
.setTargetAlpha(getWindowAlpha(progress))
|
||||
.setCornerRadius(cornerRadius);
|
||||
|
||||
mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
|
||||
if (mFIV != null) {
|
||||
mFIV.update(currentRect, 1f, progress,
|
||||
mWindowAlphaThreshold, mMatrix.mapRadius(cornerRadius), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildTargetParams(
|
||||
Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
|
||||
builder.withMatrix(mMatrix)
|
||||
.withWindowCrop(mCropRect)
|
||||
.withCornerRadius(params.getCornerRadius());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
if (mFIV != null) {
|
||||
mFIV.fastFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mHomeAnim.dispatchOnStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationSuccess(Animator animator) {
|
||||
mHomeAnim.getAnimationPlayer().end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,350 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL;
|
||||
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Matrix.ScaleToFit;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.touch.PagedOrientationHandler;
|
||||
import com.android.launcher3.views.FloatingIconView;
|
||||
import com.android.quickstep.util.RectFSpringAnim;
|
||||
import com.android.quickstep.util.TaskViewSimulator;
|
||||
import com.android.quickstep.util.TransformParams;
|
||||
import com.android.quickstep.util.TransformParams.BuilderProxy;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
|
||||
|
||||
public abstract class SwipeUpAnimationLogic {
|
||||
|
||||
protected static final Rect TEMP_RECT = new Rect();
|
||||
private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
|
||||
|
||||
protected DeviceProfile mDp;
|
||||
|
||||
protected final Context mContext;
|
||||
protected final RecentsAnimationDeviceState mDeviceState;
|
||||
protected final GestureState mGestureState;
|
||||
protected final TaskViewSimulator mTaskViewSimulator;
|
||||
|
||||
protected final TransformParams mTransformParams;
|
||||
|
||||
// Shift in the range of [0, 1].
|
||||
// 0 => preview snapShot is completely visible, and hotseat is completely translated down
|
||||
// 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
|
||||
// visible.
|
||||
protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
|
||||
|
||||
// The distance needed to drag to reach the task size in recents.
|
||||
protected int mTransitionDragLength;
|
||||
// How much further we can drag past recents, as a factor of mTransitionDragLength.
|
||||
protected float mDragLengthFactor = 1;
|
||||
// Start resisting when swiping past this factor of mTransitionDragLength.
|
||||
private float mDragLengthFactorStartPullback = 1f;
|
||||
// This is how far down we can scale down, where 0f is full screen and 1f is recents.
|
||||
private float mDragLengthFactorMaxPullback = 1f;
|
||||
|
||||
protected AnimatorPlaybackController mWindowTransitionController;
|
||||
|
||||
public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
|
||||
GestureState gestureState, TransformParams transformParams) {
|
||||
mContext = context;
|
||||
mDeviceState = deviceState;
|
||||
mGestureState = gestureState;
|
||||
mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface());
|
||||
mTransformParams = transformParams;
|
||||
}
|
||||
|
||||
protected void initTransitionEndpoints(DeviceProfile dp) {
|
||||
mDp = dp;
|
||||
|
||||
mTaskViewSimulator.setDp(dp);
|
||||
mTaskViewSimulator.setLayoutRotation(
|
||||
mDeviceState.getCurrentActiveRotation(),
|
||||
mDeviceState.getDisplayRotation());
|
||||
mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
|
||||
dp, mContext, TEMP_RECT,
|
||||
mTaskViewSimulator.getOrientationState().getOrientationHandler());
|
||||
|
||||
if (mDeviceState.isFullyGesturalNavMode()) {
|
||||
// We can drag all the way to the top of the screen.
|
||||
mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
|
||||
|
||||
float startScale = mTaskViewSimulator.getFullScreenScale();
|
||||
// Start pulling back when RecentsView scale is 0.75f, and let it go down to 0.5f.
|
||||
mDragLengthFactorStartPullback = (0.75f - startScale) / (1 - startScale);
|
||||
mDragLengthFactorMaxPullback = (0.5f - startScale) / (1 - startScale);
|
||||
} else {
|
||||
mDragLengthFactor = 1;
|
||||
mDragLengthFactorStartPullback = mDragLengthFactorMaxPullback = 1;
|
||||
}
|
||||
|
||||
PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2);
|
||||
mTaskViewSimulator.addAppToOverviewAnim(pa, t -> t * mDragLengthFactor);
|
||||
mWindowTransitionController = pa.createPlaybackController();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void updateDisplacement(float displacement) {
|
||||
// We are moving in the negative x/y direction
|
||||
displacement = -displacement;
|
||||
float shift;
|
||||
if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
|
||||
shift = mDragLengthFactor;
|
||||
} else {
|
||||
float translation = Math.max(displacement, 0);
|
||||
shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
|
||||
if (shift > mDragLengthFactorStartPullback) {
|
||||
float pullbackProgress = Utilities.getProgress(shift,
|
||||
mDragLengthFactorStartPullback, mDragLengthFactor);
|
||||
pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
|
||||
shift = mDragLengthFactorStartPullback + pullbackProgress
|
||||
* (mDragLengthFactorMaxPullback - mDragLengthFactorStartPullback);
|
||||
}
|
||||
}
|
||||
|
||||
mCurrentShift.updateValue(shift);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the value of {@link #mCurrentShift} changes
|
||||
*/
|
||||
@UiThread
|
||||
public abstract void updateFinalShift();
|
||||
|
||||
protected PagedOrientationHandler getOrientationHandler() {
|
||||
return mTaskViewSimulator.getOrientationState().getOrientationHandler();
|
||||
}
|
||||
|
||||
protected abstract class HomeAnimationFactory {
|
||||
|
||||
public FloatingIconView mIconView;
|
||||
|
||||
public HomeAnimationFactory(@Nullable FloatingIconView iconView) {
|
||||
mIconView = iconView;
|
||||
}
|
||||
|
||||
public @NonNull RectF getWindowTargetRect() {
|
||||
PagedOrientationHandler orientationHandler = getOrientationHandler();
|
||||
DeviceProfile dp = mDp;
|
||||
final int halfIconSize = dp.iconSizePx / 2;
|
||||
float primaryDimension = orientationHandler
|
||||
.getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx);
|
||||
float secondaryDimension = orientationHandler
|
||||
.getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx);
|
||||
final float targetX = primaryDimension / 2f;
|
||||
final float targetY = secondaryDimension - dp.hotseatBarSizePx;
|
||||
// Fallback to animate to center of screen.
|
||||
return new RectF(targetX - halfIconSize, targetY - halfIconSize,
|
||||
targetX + halfIconSize, targetY + halfIconSize);
|
||||
}
|
||||
|
||||
public abstract @NonNull AnimatorPlaybackController createActivityAnimationToHome();
|
||||
|
||||
public void playAtomicAnimation(float velocity) {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an animation that transforms the current app window into the home app.
|
||||
* @param startProgress The progress of {@link #mCurrentShift} to start the window from.
|
||||
* @param homeAnimationFactory The home animation factory.
|
||||
*/
|
||||
protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
|
||||
HomeAnimationFactory homeAnimationFactory) {
|
||||
final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
|
||||
final FloatingIconView fiv = homeAnimationFactory.mIconView;
|
||||
final boolean isFloatingIconView = fiv != null;
|
||||
|
||||
mWindowTransitionController.setPlayFraction(startProgress / mDragLengthFactor);
|
||||
mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress));
|
||||
RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
|
||||
|
||||
// Matrix to map a rect in Launcher space to window space
|
||||
Matrix homeToWindowPositionMap = new Matrix();
|
||||
mTaskViewSimulator.applyWindowToHomeRotation(homeToWindowPositionMap);
|
||||
|
||||
final RectF startRect = new RectF(cropRectF);
|
||||
mTaskViewSimulator.getCurrentMatrix().mapRect(startRect);
|
||||
// Move the startRect to Launcher space as floatingIconView runs in Launcher
|
||||
Matrix windowToHomePositionMap = new Matrix();
|
||||
homeToWindowPositionMap.invert(windowToHomePositionMap);
|
||||
windowToHomePositionMap.mapRect(startRect);
|
||||
|
||||
RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext);
|
||||
if (isFloatingIconView) {
|
||||
anim.addAnimatorListener(fiv);
|
||||
fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
|
||||
fiv.setFastFinishRunnable(anim::end);
|
||||
}
|
||||
|
||||
SpringAnimationRunner runner = new SpringAnimationRunner(
|
||||
homeAnimationFactory, cropRectF, homeToWindowPositionMap);
|
||||
anim.addOnUpdateListener(runner);
|
||||
anim.addAnimatorListener(runner);
|
||||
return anim;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param progress The progress of the animation to the home screen.
|
||||
* @return The current alpha to set on the animating app window.
|
||||
*/
|
||||
protected float getWindowAlpha(float progress) {
|
||||
// Alpha interpolates between [1, 0] between progress values [start, end]
|
||||
final float start = 0f;
|
||||
final float end = 0.85f;
|
||||
|
||||
if (progress <= start) {
|
||||
return 1f;
|
||||
}
|
||||
if (progress >= end) {
|
||||
return 0f;
|
||||
}
|
||||
return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
|
||||
}
|
||||
|
||||
protected class SpringAnimationRunner extends AnimationSuccessListener
|
||||
implements RectFSpringAnim.OnUpdateListener, BuilderProxy {
|
||||
|
||||
final Rect mCropRect = new Rect();
|
||||
final Matrix mMatrix = new Matrix();
|
||||
|
||||
final RectF mWindowCurrentRect = new RectF();
|
||||
final Matrix mHomeToWindowPositionMap;
|
||||
|
||||
final FloatingIconView mFIV;
|
||||
final AnimatorPlaybackController mHomeAnim;
|
||||
final RectF mCropRectF;
|
||||
|
||||
final float mStartRadius;
|
||||
final float mEndRadius;
|
||||
final float mWindowAlphaThreshold;
|
||||
|
||||
SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
|
||||
Matrix homeToWindowPositionMap) {
|
||||
mHomeAnim = factory.createActivityAnimationToHome();
|
||||
mCropRectF = cropRectF;
|
||||
mHomeToWindowPositionMap = homeToWindowPositionMap;
|
||||
|
||||
cropRectF.roundOut(mCropRect);
|
||||
mFIV = factory.mIconView;
|
||||
|
||||
// End on a "round-enough" radius so that the shape reveal doesn't have to do too much
|
||||
// rounding at the end of the animation.
|
||||
mStartRadius = mTaskViewSimulator.getCurrentCornerRadius();
|
||||
mEndRadius = cropRectF.width() / 2f;
|
||||
|
||||
// We want the window alpha to be 0 once this threshold is met, so that the
|
||||
// FolderIconView can be seen morphing into the icon shape.
|
||||
mWindowAlphaThreshold = mFIV != null ? 1f - SHAPE_PROGRESS_DURATION : 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(RectF currentRect, float progress) {
|
||||
mHomeAnim.setPlayFraction(progress);
|
||||
mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
|
||||
|
||||
mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
|
||||
float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
|
||||
mTransformParams
|
||||
.setTargetAlpha(getWindowAlpha(progress))
|
||||
.setCornerRadius(cornerRadius);
|
||||
|
||||
mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
|
||||
if (mFIV != null) {
|
||||
mFIV.update(currentRect, 1f, progress,
|
||||
mWindowAlphaThreshold, mMatrix.mapRadius(cornerRadius), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildTargetParams(
|
||||
Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
|
||||
builder.withMatrix(mMatrix)
|
||||
.withWindowCrop(mCropRect)
|
||||
.withCornerRadius(params.getCornerRadius());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
if (mFIV != null) {
|
||||
mFIV.fastFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mHomeAnim.dispatchOnStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationSuccess(Animator animator) {
|
||||
mHomeAnim.getAnimationPlayer().end();
|
||||
}
|
||||
}
|
||||
|
||||
public interface RunningWindowAnim {
|
||||
void end();
|
||||
|
||||
void cancel();
|
||||
|
||||
static RunningWindowAnim wrap(Animator animator) {
|
||||
return new RunningWindowAnim() {
|
||||
@Override
|
||||
public void end() {
|
||||
animator.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
animator.cancel();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
|
||||
return new RunningWindowAnim() {
|
||||
@Override
|
||||
public void end() {
|
||||
rectFSpringAnim.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
rectFSpringAnim.cancel();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package com.android.quickstep.fallback;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -30,7 +31,8 @@ import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
|
|||
/**
|
||||
* In 0-button mode, intercepts swipe up from the nav bar on FallbackRecentsView to go home.
|
||||
*/
|
||||
public class FallbackNavBarTouchController implements TouchController {
|
||||
public class FallbackNavBarTouchController implements TouchController,
|
||||
TriggerSwipeUpTouchTracker.OnSwipeUpListener {
|
||||
|
||||
private final RecentsActivity mActivity;
|
||||
@Nullable
|
||||
|
@ -44,7 +46,7 @@ public class FallbackNavBarTouchController implements TouchController {
|
|||
DefaultDisplay.INSTANCE.get(mActivity).getInfo());
|
||||
mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mActivity,
|
||||
true /* disableHorizontalSwipe */, navBarPosition,
|
||||
null /* onInterceptTouch */, this::onSwipeUp);
|
||||
null /* onInterceptTouch */, this);
|
||||
} else {
|
||||
mTriggerSwipeUpTracker = null;
|
||||
}
|
||||
|
@ -72,7 +74,11 @@ public class FallbackNavBarTouchController implements TouchController {
|
|||
return false;
|
||||
}
|
||||
|
||||
private void onSwipeUp(boolean wasFling) {
|
||||
@Override
|
||||
public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
|
||||
mActivity.<FallbackRecentsView>getOverviewPanel().startHome();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwipeUpCancelled() {}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package com.android.quickstep.inputconsumers;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PointF;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.launcher3.BaseActivity;
|
||||
|
@ -33,7 +34,8 @@ import com.android.quickstep.util.ActiveGestureLog;
|
|||
import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
|
||||
import com.android.systemui.shared.system.InputMonitorCompat;
|
||||
|
||||
public class OverviewWithoutFocusInputConsumer implements InputConsumer {
|
||||
public class OverviewWithoutFocusInputConsumer implements InputConsumer,
|
||||
TriggerSwipeUpTouchTracker.OnSwipeUpListener {
|
||||
|
||||
private final Context mContext;
|
||||
private final InputMonitorCompat mInputMonitor;
|
||||
|
@ -45,7 +47,7 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer {
|
|||
mContext = context;
|
||||
mInputMonitor = inputMonitor;
|
||||
mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, disableHorizontalSwipe,
|
||||
deviceState.getNavBarPosition(), this::onInterceptTouch, this::onSwipeUp);
|
||||
deviceState.getNavBarPosition(), this::onInterceptTouch, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -70,7 +72,8 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer {
|
|||
}
|
||||
}
|
||||
|
||||
private void onSwipeUp(boolean wasFling) {
|
||||
@Override
|
||||
public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
|
||||
mContext.startActivity(new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_HOME)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
|
@ -83,4 +86,7 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer {
|
|||
wasFling ? Touch.FLING : Touch.SWIPE, Direction.UP, containerType, pageIndex);
|
||||
activity.getUserEventDispatcher().setPreviousHomeGesture(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwipeUpCancelled() {}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_
|
|||
import android.animation.TimeInterpolator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
|
@ -74,7 +75,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
|
|||
private DeviceProfile mDp;
|
||||
|
||||
private final Matrix mMatrix = new Matrix();
|
||||
private RemoteAnimationTargetCompat mRunningTarget;
|
||||
private final Point mRunningTargetWindowPosition = new Point();
|
||||
|
||||
// Thumbnail view properties
|
||||
private final Rect mThumbnailPosition = new Rect();
|
||||
|
@ -139,13 +140,19 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
|
|||
* Sets the targets which the simulator will control
|
||||
*/
|
||||
public void setPreview(RemoteAnimationTargetCompat runningTarget) {
|
||||
mRunningTarget = runningTarget;
|
||||
setPreviewBounds(runningTarget.screenSpaceBounds, runningTarget.contentInsets);
|
||||
mRunningTargetWindowPosition.set(runningTarget.position.x, runningTarget.position.y);
|
||||
}
|
||||
|
||||
mThumbnailData.insets.set(mRunningTarget.contentInsets);
|
||||
/**
|
||||
* Sets the targets which the simulator will control
|
||||
*/
|
||||
public void setPreviewBounds(Rect bounds, Rect insets) {
|
||||
mThumbnailData.insets.set(insets);
|
||||
// TODO: What is this?
|
||||
mThumbnailData.windowingMode = WINDOWING_MODE_FULLSCREEN;
|
||||
|
||||
mThumbnailPosition.set(runningTarget.screenSpaceBounds);
|
||||
mThumbnailPosition.set(bounds);
|
||||
mLayoutValid = false;
|
||||
}
|
||||
|
||||
|
@ -199,16 +206,14 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
|
|||
postDisplayRotation(deltaRotation(
|
||||
mOrientationState.getLauncherRotation(), mOrientationState.getDisplayRotation()),
|
||||
mDp.widthPx, mDp.heightPx, matrix);
|
||||
if (mRunningTarget != null) {
|
||||
matrix.postTranslate(-mRunningTarget.position.x, -mRunningTarget.position.y);
|
||||
}
|
||||
matrix.postTranslate(-mRunningTargetWindowPosition.x, -mRunningTargetWindowPosition.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the target to the previously set parameters
|
||||
*/
|
||||
public void apply(TransformParams params) {
|
||||
if (mDp == null || mRunningTarget == null) {
|
||||
if (mDp == null || mThumbnailPosition.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!mLayoutValid) {
|
||||
|
|
|
@ -149,8 +149,12 @@ public class TriggerSwipeUpTouchTracker {
|
|||
isSwipeUp = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
|
||||
}
|
||||
|
||||
if (isSwipeUp && mOnSwipeUp != null) {
|
||||
mOnSwipeUp.onSwipeUp(wasFling);
|
||||
if (mOnSwipeUp != null) {
|
||||
if (isSwipeUp) {
|
||||
mOnSwipeUp.onSwipeUp(wasFling, new PointF(velocityX, velocityY));
|
||||
} else {
|
||||
mOnSwipeUp.onSwipeUpCancelled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,7 +165,11 @@ public class TriggerSwipeUpTouchTracker {
|
|||
/**
|
||||
* Called on touch up if a swipe up was detected.
|
||||
* @param wasFling Whether the swipe was a fling, or just passed touch slop at low velocity.
|
||||
* @param finalVelocity The final velocity of the swipe.
|
||||
*/
|
||||
void onSwipeUp(boolean wasFling);
|
||||
void onSwipeUp(boolean wasFling, PointF finalVelocity);
|
||||
|
||||
/** Called on touch up if a swipe up was not detected. */
|
||||
void onSwipeUpCancelled();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,13 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="@drawable/gesture_tutorial_ripple"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/gesture_tutorial_fake_task_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/gesture_tutorial_fake_task_view_color"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gesture_tutorial_fragment_hand_coaching"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -18,8 +18,11 @@ package com.android.quickstep.interaction;
|
|||
import static com.android.quickstep.interaction.TutorialController.TutorialType.BACK_NAVIGATION_COMPLETE;
|
||||
import static com.android.quickstep.interaction.TutorialController.TutorialType.LEFT_EDGE_BACK_NAVIGATION;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
|
||||
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
|
||||
|
@ -154,11 +157,14 @@ final class BackGestureTutorialController extends TutorialController {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onNavBarGestureAttempted(NavBarGestureResult result) {
|
||||
public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
|
||||
if (mTutorialType == BACK_NAVIGATION_COMPLETE) {
|
||||
if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
|
||||
mTutorialFragment.closeTutorial();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNavBarGestureProgress(@Nullable Float displacement) {}
|
||||
}
|
||||
|
|
|
@ -15,19 +15,134 @@
|
|||
*/
|
||||
package com.android.quickstep.interaction;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL;
|
||||
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
|
||||
import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION;
|
||||
import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Build;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.WindowInsets.Type;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.quickstep.AnimatedFloat;
|
||||
import com.android.quickstep.GestureState;
|
||||
import com.android.quickstep.OverviewComponentObserver;
|
||||
import com.android.quickstep.RecentsAnimationDeviceState;
|
||||
import com.android.quickstep.SwipeUpAnimationLogic;
|
||||
import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim;
|
||||
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
|
||||
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
|
||||
import com.android.quickstep.util.RectFSpringAnim;
|
||||
import com.android.quickstep.util.TransformParams;
|
||||
import com.android.systemui.shared.system.QuickStepContract;
|
||||
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
|
||||
|
||||
/** A {@link TutorialController} for the Home tutorial. */
|
||||
@TargetApi(Build.VERSION_CODES.R)
|
||||
final class HomeGestureTutorialController extends TutorialController {
|
||||
|
||||
private float mFakeTaskViewRadius;
|
||||
private Rect mFakeTaskViewRect = new Rect();
|
||||
|
||||
private final ViewSwipeUpAnimation mViewSwipeUpAnimation;
|
||||
private RunningWindowAnim mRunningWindowAnim;
|
||||
|
||||
HomeGestureTutorialController(HomeGestureTutorialFragment fragment, TutorialType tutorialType) {
|
||||
super(fragment, tutorialType);
|
||||
|
||||
RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext);
|
||||
OverviewComponentObserver observer = new OverviewComponentObserver(mContext, deviceState);
|
||||
mViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState,
|
||||
new GestureState(observer, -1));
|
||||
observer.onDestroy();
|
||||
|
||||
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext)
|
||||
.getDeviceProfile(mContext)
|
||||
.copy(mContext);
|
||||
Insets insets = mContext.getSystemService(WindowManager.class)
|
||||
.getCurrentWindowMetrics()
|
||||
.getWindowInsets()
|
||||
.getInsets(Type.systemBars());
|
||||
dp.updateInsets(new Rect(insets.left, insets.top, insets.right, insets.bottom));
|
||||
mViewSwipeUpAnimation.initDp(dp);
|
||||
|
||||
mFakeTaskViewRadius = QuickStepContract.getWindowCornerRadius(mContext.getResources());
|
||||
|
||||
mFakeTaskView.setClipToOutline(true);
|
||||
mFakeTaskView.setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
outline.setRoundRect(mFakeTaskViewRect, mFakeTaskViewRadius);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void cancelRunningAnimation() {
|
||||
if (mRunningWindowAnim != null) {
|
||||
mRunningWindowAnim.cancel();
|
||||
}
|
||||
mRunningWindowAnim = null;
|
||||
}
|
||||
|
||||
/** Fades the task view, optionally after animating to a fake Overview. */
|
||||
private void fadeOutFakeTaskView(boolean toOverviewFirst, @Nullable Runnable onEndRunnable) {
|
||||
cancelRunningAnimation();
|
||||
PendingAnimation anim = new PendingAnimation(300);
|
||||
AnimatorListenerAdapter resetTaskView = new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation, boolean isReverse) {
|
||||
mFakeTaskView.setVisibility(View.INVISIBLE);
|
||||
mFakeTaskView.setAlpha(1);
|
||||
mRunningWindowAnim = null;
|
||||
}
|
||||
};
|
||||
if (toOverviewFirst) {
|
||||
anim.setFloat(mViewSwipeUpAnimation.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation, boolean isReverse) {
|
||||
PendingAnimation fadeAnim = new PendingAnimation(300);
|
||||
fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL);
|
||||
fadeAnim.addListener(resetTaskView);
|
||||
AnimatorSet animset = fadeAnim.buildAnim();
|
||||
animset.setStartDelay(100);
|
||||
animset.start();
|
||||
mRunningWindowAnim = RunningWindowAnim.wrap(animset);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
anim.setViewAlpha(mFakeTaskView, 0, ACCEL);
|
||||
anim.addListener(resetTaskView);
|
||||
}
|
||||
if (onEndRunnable != null) {
|
||||
anim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable));
|
||||
}
|
||||
AnimatorSet animset = anim.buildAnim();
|
||||
animset.start();
|
||||
mRunningWindowAnim = RunningWindowAnim.wrap(animset);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -85,22 +200,35 @@ final class HomeGestureTutorialController extends TutorialController {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onNavBarGestureAttempted(NavBarGestureResult result) {
|
||||
public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
|
||||
switch (mTutorialType) {
|
||||
case HOME_NAVIGATION:
|
||||
switch (result) {
|
||||
case HOME_GESTURE_COMPLETED:
|
||||
case HOME_GESTURE_COMPLETED: {
|
||||
hideFeedback();
|
||||
cancelRunningAnimation();
|
||||
hideHandCoachingAnimation();
|
||||
mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE);
|
||||
RectFSpringAnim rectAnim =
|
||||
mViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity);
|
||||
// After home animation finishes, fade out and then move to the next screen.
|
||||
rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable(
|
||||
() -> fadeOutFakeTaskView(false,
|
||||
() -> mTutorialFragment.changeController(
|
||||
HOME_NAVIGATION_COMPLETE))));
|
||||
mRunningWindowAnim = RunningWindowAnim.wrap(rectAnim);
|
||||
break;
|
||||
}
|
||||
case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
|
||||
case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE:
|
||||
showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge);
|
||||
break;
|
||||
case OVERVIEW_GESTURE_COMPLETED:
|
||||
showFeedback(R.string.home_gesture_feedback_overview_detected);
|
||||
fadeOutFakeTaskView(true, () ->
|
||||
showFeedback(R.string.home_gesture_feedback_overview_detected));
|
||||
break;
|
||||
case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
|
||||
case HOME_OR_OVERVIEW_CANCELLED:
|
||||
fadeOutFakeTaskView(false, null);
|
||||
showFeedback(R.string.home_gesture_feedback_wrong_swipe_direction);
|
||||
break;
|
||||
}
|
||||
|
@ -112,4 +240,94 @@ final class HomeGestureTutorialController extends TutorialController {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNavBarGestureProgress(@Nullable Float displacement) {
|
||||
if (displacement == null || mTutorialType == HOME_NAVIGATION_COMPLETE) {
|
||||
mFakeTaskView.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
mFakeTaskView.setVisibility(View.VISIBLE);
|
||||
if (mRunningWindowAnim == null) {
|
||||
mViewSwipeUpAnimation.updateDisplacement(displacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewSwipeUpAnimation extends SwipeUpAnimationLogic {
|
||||
|
||||
ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState,
|
||||
GestureState gestureState) {
|
||||
super(context, deviceState, gestureState, new FakeTransformParams());
|
||||
}
|
||||
|
||||
void initDp(DeviceProfile dp) {
|
||||
initTransitionEndpoints(dp);
|
||||
mTaskViewSimulator.setPreviewBounds(
|
||||
new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFinalShift() {
|
||||
float progress = mCurrentShift.value / mDragLengthFactor;
|
||||
mWindowTransitionController.setPlayFraction(progress);
|
||||
mTaskViewSimulator.apply(mTransformParams);
|
||||
}
|
||||
|
||||
AnimatedFloat getCurrentShift() {
|
||||
return mCurrentShift;
|
||||
}
|
||||
|
||||
RectFSpringAnim handleSwipeUpToHome(PointF velocity) {
|
||||
PointF velocityPxPerMs = new PointF(velocity.x, velocity.y);
|
||||
float currentShift = mCurrentShift.value;
|
||||
final float startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
|
||||
* getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
|
||||
float distanceToTravel = (1 - currentShift) * mTransitionDragLength;
|
||||
|
||||
// we want the page's snap velocity to approximately match the velocity at
|
||||
// which the user flings, so we scale the duration by a value near to the
|
||||
// derivative of the scroll interpolator at zero, ie. 2.
|
||||
long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
|
||||
long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
|
||||
HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory(null) {
|
||||
@Override
|
||||
public AnimatorPlaybackController createActivityAnimationToHome() {
|
||||
return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RectF getWindowTargetRect() {
|
||||
int fakeHomeIconSizePx = mDp.allAppsIconSizePx;
|
||||
int fakeHomeIconLeft = (mDp.widthPx - fakeHomeIconSizePx) / 2;
|
||||
int fakeHomeIconTop = mDp.heightPx - (mDp.allAppsCellHeightPx * 3);
|
||||
return new RectF(fakeHomeIconLeft, fakeHomeIconTop,
|
||||
fakeHomeIconLeft + fakeHomeIconSizePx,
|
||||
fakeHomeIconTop + fakeHomeIconSizePx);
|
||||
}
|
||||
};
|
||||
RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory);
|
||||
windowAnim.start(mContext, velocityPxPerMs);
|
||||
return windowAnim;
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeTransformParams extends TransformParams {
|
||||
|
||||
@Override
|
||||
public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
|
||||
SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
|
||||
proxy.onBuildTargetParams(builder, null, this);
|
||||
return new SurfaceParams[] {builder.build()};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applySurfaceParams(SurfaceParams[] params) {
|
||||
SurfaceParams p = params[0];
|
||||
mFakeTaskView.setAnimationMatrix(p.matrix);
|
||||
mFakeTaskViewRect.set(p.windowCrop);
|
||||
mFakeTaskViewRadius = p.cornerRadius;
|
||||
mFakeTaskView.invalidateOutline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package com.android.quickstep.interaction;
|
|||
|
||||
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_GESTURE_COMPLETED;
|
||||
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_NOT_STARTED_TOO_FAR_FROM_EDGE;
|
||||
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_CANCELLED;
|
||||
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION;
|
||||
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_GESTURE_COMPLETED;
|
||||
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE;
|
||||
|
@ -24,19 +25,23 @@ import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestu
|
|||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.view.Display;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.View.OnTouchListener;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.ResourceUtils;
|
||||
import com.android.quickstep.SysUINavigationMode.Mode;
|
||||
import com.android.quickstep.util.NavBarPosition;
|
||||
import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
|
||||
|
||||
/** Utility class to handle home gestures. */
|
||||
public class NavBarGestureHandler implements OnTouchListener {
|
||||
public class NavBarGestureHandler implements OnTouchListener,
|
||||
TriggerSwipeUpTouchTracker.OnSwipeUpListener {
|
||||
|
||||
private static final String LOG_TAG = "NavBarGestureHandler";
|
||||
|
||||
|
@ -44,6 +49,7 @@ public class NavBarGestureHandler implements OnTouchListener {
|
|||
private final TriggerSwipeUpTouchTracker mSwipeUpTouchTracker;
|
||||
private int mBottomGestureHeight;
|
||||
private boolean mTouchCameFromNavBar;
|
||||
private float mDownY;
|
||||
private NavBarGestureAttemptCallback mGestureCallback;
|
||||
|
||||
NavBarGestureHandler(Context context) {
|
||||
|
@ -55,10 +61,11 @@ public class NavBarGestureHandler implements OnTouchListener {
|
|||
displayRotation = display.getRotation();
|
||||
display.getRealSize(mDisplaySize);
|
||||
}
|
||||
mDownY = mDisplaySize.y;
|
||||
mSwipeUpTouchTracker =
|
||||
new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
|
||||
new NavBarPosition(Mode.NO_BUTTON, displayRotation),
|
||||
null /*onInterceptTouch*/, this::onSwipeUp);
|
||||
null /*onInterceptTouch*/, this);
|
||||
|
||||
final Resources resources = context.getResources();
|
||||
mBottomGestureHeight =
|
||||
|
@ -73,16 +80,26 @@ public class NavBarGestureHandler implements OnTouchListener {
|
|||
mGestureCallback = null;
|
||||
}
|
||||
|
||||
private void onSwipeUp(boolean wasFling) {
|
||||
@Override
|
||||
public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
|
||||
if (mGestureCallback == null) {
|
||||
return;
|
||||
}
|
||||
finalVelocity.set(finalVelocity.x / 1000, finalVelocity.y / 1000);
|
||||
if (mTouchCameFromNavBar) {
|
||||
mGestureCallback.onNavBarGestureAttempted(wasFling
|
||||
? HOME_GESTURE_COMPLETED : OVERVIEW_GESTURE_COMPLETED);
|
||||
? HOME_GESTURE_COMPLETED : OVERVIEW_GESTURE_COMPLETED, finalVelocity);
|
||||
} else {
|
||||
mGestureCallback.onNavBarGestureAttempted(wasFling
|
||||
? HOME_NOT_STARTED_TOO_FAR_FROM_EDGE : OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE);
|
||||
? HOME_NOT_STARTED_TOO_FAR_FROM_EDGE : OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE,
|
||||
finalVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwipeUpCancelled() {
|
||||
if (mGestureCallback != null) {
|
||||
mGestureCallback.onNavBarGestureAttempted(HOME_OR_OVERVIEW_CANCELLED, new PointF());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,15 +108,22 @@ public class NavBarGestureHandler implements OnTouchListener {
|
|||
int action = motionEvent.getAction();
|
||||
boolean intercepted = mSwipeUpTouchTracker.interceptedTouch();
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
mTouchCameFromNavBar = motionEvent.getRawY() >= mDisplaySize.y - mBottomGestureHeight;
|
||||
mDownY = motionEvent.getY();
|
||||
mTouchCameFromNavBar = mDownY >= mDisplaySize.y - mBottomGestureHeight;
|
||||
if (!mTouchCameFromNavBar) {
|
||||
mGestureCallback.setNavBarGestureProgress(null);
|
||||
}
|
||||
mSwipeUpTouchTracker.init();
|
||||
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||
if (mGestureCallback != null && !intercepted && mTouchCameFromNavBar) {
|
||||
mGestureCallback.onNavBarGestureAttempted(
|
||||
HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION);
|
||||
HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION, new PointF());
|
||||
intercepted = true;
|
||||
}
|
||||
}
|
||||
if (mTouchCameFromNavBar && mGestureCallback != null) {
|
||||
mGestureCallback.setNavBarGestureProgress(motionEvent.getY() - mDownY);
|
||||
}
|
||||
mSwipeUpTouchTracker.onMotionEvent(motionEvent);
|
||||
return intercepted;
|
||||
}
|
||||
|
@ -110,12 +134,16 @@ public class NavBarGestureHandler implements OnTouchListener {
|
|||
OVERVIEW_GESTURE_COMPLETED,
|
||||
HOME_NOT_STARTED_TOO_FAR_FROM_EDGE,
|
||||
OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE,
|
||||
HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION // Side swipe on nav bar.
|
||||
HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION, // Side swipe on nav bar.
|
||||
HOME_OR_OVERVIEW_CANCELLED
|
||||
}
|
||||
|
||||
/** Callback to let the UI react to attempted nav bar gestures. */
|
||||
interface NavBarGestureAttemptCallback {
|
||||
/** Called whenever any touch is completed. */
|
||||
void onNavBarGestureAttempted(NavBarGestureResult result);
|
||||
void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity);
|
||||
|
||||
/** Indicates how far a touch originating in the nav bar has moved from the nav bar. */
|
||||
void setNavBarGestureProgress(@Nullable Float displacement);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package com.android.quickstep.interaction;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.RippleDrawable;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
@ -39,11 +40,13 @@ abstract class TutorialController implements BackGestureAttemptCallback,
|
|||
|
||||
final TutorialFragment mTutorialFragment;
|
||||
TutorialType mTutorialType;
|
||||
final Context mContext;
|
||||
|
||||
final ImageButton mCloseButton;
|
||||
final TextView mTitleTextView;
|
||||
final TextView mSubtitleTextView;
|
||||
final TextView mFeedbackView;
|
||||
final View mFakeTaskView;
|
||||
final View mRippleView;
|
||||
final RippleDrawable mRippleDrawable;
|
||||
final TutorialHandAnimation mHandCoachingAnimation;
|
||||
|
@ -55,6 +58,7 @@ abstract class TutorialController implements BackGestureAttemptCallback,
|
|||
TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
|
||||
mTutorialFragment = tutorialFragment;
|
||||
mTutorialType = tutorialType;
|
||||
mContext = mTutorialFragment.getContext();
|
||||
|
||||
View rootView = tutorialFragment.getRootView();
|
||||
mCloseButton = rootView.findViewById(R.id.gesture_tutorial_fragment_close_button);
|
||||
|
@ -62,6 +66,7 @@ abstract class TutorialController implements BackGestureAttemptCallback,
|
|||
mTitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_title_view);
|
||||
mSubtitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_subtitle_view);
|
||||
mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view);
|
||||
mFakeTaskView = rootView.findViewById(R.id.gesture_tutorial_fake_task_view);
|
||||
mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view);
|
||||
mRippleDrawable = (RippleDrawable) mRippleView.getBackground();
|
||||
mHandCoachingAnimation = tutorialFragment.getHandAnimation();
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
<color name="gesture_tutorial_subtitle_color">#99000000</color> <!-- 60% black -->
|
||||
<color name="gesture_tutorial_feedback_color">#FF000000</color>
|
||||
<color name="gesture_tutorial_ripple_color">#A0C2F9</color> <!-- Light Blue -->
|
||||
<color name="gesture_tutorial_fake_task_view_color">#6DA1FF</color> <!-- Light Blue -->
|
||||
<color name="gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
|
||||
<color name="gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->
|
||||
|
||||
|
|
Loading…
Reference in New Issue