Files
lawnchair/src/com/android/launcher3/touch/BaseSwipeDetector.java
Sebastian Franco 3b86202a95 Fling to close tasks in the GestureNav can be too small.
The problem is that the velocity threshold to activate the fling
was set using pixels per second, so a better aproach would is to
use dp/s. So now there is a variable set in the dimes.xml file
called base_swift_detector_fling_release_velocity.

Test: Manually tested
Fix: 201252634
Change-Id: Ief14f25de136dead74f03cb24d2120b67900239e
2021-11-16 14:46:36 -06:00

292 lines
10 KiB
Java

/*
* Copyright (C) 2017 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 android.view.MotionEvent.INVALID_POINTER_ID;
import android.content.Context;
import android.graphics.PointF;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
import java.util.LinkedList;
import java.util.Queue;
/**
* Scroll/drag/swipe gesture detector.
*
* Definition of swipe is different from android system in that this detector handles
* 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before
* swipe action happens.
*
* @see SingleAxisSwipeDetector
* @see BothAxesSwipeDetector
*/
public abstract class BaseSwipeDetector {
private static final boolean DBG = false;
private static final String TAG = "BaseSwipeDetector";
private static final float ANIMATION_DURATION = 1200;
private static final PointF sTempPoint = new PointF();
private final float mReleaseVelocity;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
protected final boolean mIsRtl;
protected final float mTouchSlop;
protected final float mMaxVelocity;
private final Queue<Runnable> mSetStateQueue = new LinkedList<>();
private int mActivePointerId = INVALID_POINTER_ID;
private VelocityTracker mVelocityTracker;
private PointF mLastDisplacement = new PointF();
private PointF mDisplacement = new PointF();
protected PointF mSubtractDisplacement = new PointF();
@VisibleForTesting ScrollState mState = ScrollState.IDLE;
private boolean mIsSettingState;
protected boolean mIgnoreSlopWhenSettling;
protected Context mContext;
private enum ScrollState {
IDLE,
DRAGGING, // onDragStart, onDrag
SETTLING // onDragEnd
}
protected BaseSwipeDetector(@NonNull Context context, @NonNull ViewConfiguration config,
boolean isRtl) {
mTouchSlop = config.getScaledTouchSlop();
mMaxVelocity = config.getScaledMaximumFlingVelocity();
mIsRtl = isRtl;
mContext = context;
mReleaseVelocity = mContext.getResources()
.getDimensionPixelSize(R.dimen.base_swift_detector_fling_release_velocity);
}
public static long calculateDuration(float velocity, float progressNeeded) {
// TODO: make these values constants after tuning.
float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
float travelDistance = Math.max(0.2f, progressNeeded);
long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
if (DBG) {
Log.d(TAG, String.format(
"calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
}
return duration;
}
public int getDownX() {
return (int) mDownPos.x;
}
public int getDownY() {
return (int) mDownPos.y;
}
/**
* There's no touch and there's no animation.
*/
public boolean isIdleState() {
return mState == ScrollState.IDLE;
}
public boolean isSettlingState() {
return mState == ScrollState.SETTLING;
}
public boolean isDraggingState() {
return mState == ScrollState.DRAGGING;
}
public boolean isDraggingOrSettling() {
return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
}
public void finishedScrolling() {
setState(ScrollState.IDLE);
}
public boolean isFling(float velocity) {
return Math.abs(velocity) > mReleaseVelocity;
}
public boolean onTouchEvent(MotionEvent ev) {
int actionMasked = ev.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
mVelocityTracker.clear();
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
mLastDisplacement.set(0, 0);
mDisplacement.set(0, 0);
if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
setState(ScrollState.DRAGGING);
}
break;
//case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_POINTER_UP:
int ptrIdx = ev.getActionIndex();
int ptrId = ev.getPointerId(ptrIdx);
if (ptrId == mActivePointerId) {
final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
mDownPos.set(
ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
mActivePointerId = ev.getPointerId(newPointerIdx);
}
break;
case MotionEvent.ACTION_MOVE:
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == INVALID_POINTER_ID) {
break;
}
mDisplacement.set(ev.getX(pointerIndex) - mDownPos.x,
ev.getY(pointerIndex) - mDownPos.y);
if (mIsRtl) {
mDisplacement.x = -mDisplacement.x;
}
// handle state and listener calls.
if (mState != ScrollState.DRAGGING && shouldScrollStart(mDisplacement)) {
setState(ScrollState.DRAGGING);
}
if (mState == ScrollState.DRAGGING) {
reportDragging(ev);
}
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// These are synthetic events and there is no need to update internal values.
if (mState == ScrollState.DRAGGING) {
setState(ScrollState.SETTLING);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
break;
default:
break;
}
return true;
}
//------------------- ScrollState transition diagram -----------------------------------
//
// IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING
// DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
// SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
// SETTLING -> (View settled) -> IDLE
private void setState(ScrollState newState) {
if (mIsSettingState) {
mSetStateQueue.add(() -> setState(newState));
return;
}
mIsSettingState = true;
if (DBG) {
Log.d(TAG, "setState:" + mState + "->" + newState);
}
// onDragStart and onDragEnd is reported ONLY on state transition
if (newState == ScrollState.DRAGGING) {
initializeDragging();
if (mState == ScrollState.IDLE) {
reportDragStart(false /* recatch */);
} else if (mState == ScrollState.SETTLING) {
reportDragStart(true /* recatch */);
}
}
if (newState == ScrollState.SETTLING) {
reportDragEnd();
}
mState = newState;
mIsSettingState = false;
if (!mSetStateQueue.isEmpty()) {
mSetStateQueue.remove().run();
}
}
private void initializeDragging() {
if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
mSubtractDisplacement.set(0, 0);
} else {
mSubtractDisplacement.x = mDisplacement.x > 0 ? mTouchSlop : -mTouchSlop;
mSubtractDisplacement.y = mDisplacement.y > 0 ? mTouchSlop : -mTouchSlop;
}
}
protected abstract boolean shouldScrollStart(PointF displacement);
private void reportDragStart(boolean recatch) {
reportDragStartInternal(recatch);
if (DBG) {
Log.d(TAG, "onDragStart recatch:" + recatch);
}
}
protected abstract void reportDragStartInternal(boolean recatch);
private void reportDragging(MotionEvent event) {
if (mDisplacement != mLastDisplacement) {
if (DBG) {
Log.d(TAG, String.format("onDrag disp=%s", mDisplacement));
}
mLastDisplacement.set(mDisplacement);
sTempPoint.set(mDisplacement.x - mSubtractDisplacement.x,
mDisplacement.y - mSubtractDisplacement.y);
reportDraggingInternal(sTempPoint, event);
}
}
protected abstract void reportDraggingInternal(PointF displacement, MotionEvent event);
private void reportDragEnd() {
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
PointF velocity = new PointF(mVelocityTracker.getXVelocity() / 1000,
mVelocityTracker.getYVelocity() / 1000);
if (mIsRtl) {
velocity.x = -velocity.x;
}
if (DBG) {
Log.d(TAG, String.format("onScrollEnd disp=%.1s, velocity=%.1s",
mDisplacement, velocity));
}
reportDragEndInternal(velocity);
}
protected abstract void reportDragEndInternal(PointF velocity);
}