Files
lawnchair/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java

242 lines
8.8 KiB
Java
Raw Normal View History

/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.touch;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.util.Log;
import android.view.MotionEvent;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.TouchController;
/**
* TouchController for handling state changes
*/
public abstract class AbstractStateChangeTouchController extends AnimatorListenerAdapter
implements TouchController, SwipeDetector.Listener {
private static final String TAG = "ASCTouchController";
public static final float RECATCH_REJECTION_FRACTION = .0875f;
public static final int SINGLE_FRAME_MS = 16;
// Progress after which the transition is assumed to be a success in case user does not fling
public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
protected final Launcher mLauncher;
protected final SwipeDetector mDetector;
private boolean mNoIntercept;
protected int mStartContainerType;
protected LauncherState mFromState;
protected LauncherState mToState;
protected AnimatorPlaybackController mCurrentAnimation;
private float mStartProgress;
// Ratio of transition process [0, 1] to drag displacement (px)
private float mProgressMultiplier;
public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
mLauncher = l;
mDetector = new SwipeDetector(l, this, dir);
}
protected abstract boolean canInterceptTouch(MotionEvent ev);
/**
* Initializes the {@code mFromState} and {@code mToState} and swipe direction to use for
* the detector. In can of disabling swipe, return 0.
*/
protected abstract int getSwipeDirection(MotionEvent ev);
@Override
public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = !canInterceptTouch(ev);
if (mNoIntercept) {
return false;
}
// Now figure out which direction scroll events the controller will start
// calling the callbacks.
final int directionsToDetectScroll;
boolean ignoreSlopWhenSettling = false;
if (mCurrentAnimation != null) {
if (mCurrentAnimation.getProgressFraction() > 1 - RECATCH_REJECTION_FRACTION) {
directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
} else if (mCurrentAnimation.getProgressFraction() < RECATCH_REJECTION_FRACTION ) {
directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
} else {
directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
ignoreSlopWhenSettling = true;
}
} else {
directionsToDetectScroll = getSwipeDirection(ev);
if (directionsToDetectScroll == 0) {
mNoIntercept = true;
return false;
}
}
mDetector.setDetectableScrollConditions(
directionsToDetectScroll, ignoreSlopWhenSettling);
}
if (mNoIntercept) {
return false;
}
onControllerTouchEvent(ev);
return mDetector.isDraggingOrSettling();
}
@Override
public final boolean onControllerTouchEvent(MotionEvent ev) {
return mDetector.onTouchEvent(ev);
}
protected float getShiftRange() {
return mLauncher.getAllAppsController().getShiftRange();
}
protected abstract float initCurrentAnimation();
@Override
public void onDragStart(boolean start) {
if (mCurrentAnimation == null) {
mStartProgress = 0;
mProgressMultiplier = initCurrentAnimation();
mCurrentAnimation.getTarget().addListener(this);
mCurrentAnimation.dispatchOnStart();
} else {
mCurrentAnimation.pause();
mStartProgress = mCurrentAnimation.getProgressFraction();
}
}
@Override
public boolean onDrag(float displacement, float velocity) {
float deltaProgress = mProgressMultiplier * displacement;
updateProgress(deltaProgress + mStartProgress);
return true;
}
protected void updateProgress(float fraction) {
mCurrentAnimation.setPlayFraction(fraction);
}
@Override
public void onDragEnd(float velocity, boolean fling) {
final int logAction;
final LauncherState targetState;
final float progress = mCurrentAnimation.getProgressFraction();
if (fling) {
logAction = Touch.FLING;
targetState =
Float.compare(Math.signum(velocity), Math.signum(mProgressMultiplier)) == 0
? mToState : mFromState;
// snap to top or bottom using the release velocity
} else {
logAction = Touch.SWIPE;
targetState = (progress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
}
final float endProgress;
final float startProgress;
final long duration;
if (targetState == mToState) {
endProgress = 1;
if (progress >= 1) {
duration = 0;
startProgress = 1;
} else {
startProgress = Utilities.boundToRange(
progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
duration = SwipeDetector.calculateDuration(velocity,
endProgress - Math.max(progress, 0));
}
} else {
endProgress = 0;
if (progress <= 0) {
duration = 0;
startProgress = 0;
} else {
startProgress = Utilities.boundToRange(
progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
duration = SwipeDetector.calculateDuration(velocity,
Math.min(progress, 1) - endProgress);
}
}
mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState, logAction));
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
anim.setFloatValues(startProgress, endProgress);
updateSwipeCompleteAnimation(anim, duration, targetState, velocity, fling);
anim.start();
}
protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
LauncherState targetState, float velocity, boolean isFling) {
animator.setDuration(expectedDuration)
.setInterpolator(scrollInterpolatorForVelocity(velocity));
}
protected int getDirectionForLog() {
return mToState.ordinal > mFromState.ordinal ? Direction.UP : Direction.DOWN;
}
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
if (targetState != mFromState) {
// Transition complete. log the action
mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
getDirectionForLog(),
mStartContainerType,
mFromState.containerType,
mToState.containerType,
mLauncher.getWorkspace().getCurrentPage());
}
clearState();
mLauncher.getStateManager().goToState(targetState, false /* animated */);
}
protected void clearState() {
mCurrentAnimation = null;
mDetector.finishedScrolling();
}
@Override
public void onAnimationCancel(Animator animation) {
if (mCurrentAnimation != null && animation == mCurrentAnimation.getOriginalTarget()) {
Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
clearState();
}
}
}