FakeDrag.java
/*
* Copyright 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 androidx.viewpager2.widget;
import static androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import androidx.annotation.UiThread;
import androidx.recyclerview.widget.RecyclerView;
/**
* Provides fake dragging functionality to {@link ViewPager2}.
*/
final class FakeDrag {
private final ViewPager2 mViewPager;
private final ScrollEventAdapter mScrollEventAdapter;
private final RecyclerView mRecyclerView;
private VelocityTracker mVelocityTracker;
private int mMaximumVelocity;
private float mRequestedDragDistance;
private int mActualDraggedDistance;
private long mFakeDragBeginTime;
FakeDrag(ViewPager2 viewPager, ScrollEventAdapter scrollEventAdapter,
RecyclerView recyclerView) {
mViewPager = viewPager;
mScrollEventAdapter = scrollEventAdapter;
mRecyclerView = recyclerView;
}
boolean isFakeDragging() {
return mScrollEventAdapter.isFakeDragging();
}
@UiThread
boolean beginFakeDrag() {
if (mScrollEventAdapter.isDragging()) {
return false;
}
mRequestedDragDistance = mActualDraggedDistance = 0;
mFakeDragBeginTime = SystemClock.uptimeMillis();
beginFakeVelocityTracker();
mScrollEventAdapter.notifyBeginFakeDrag();
if (!mScrollEventAdapter.isIdle()) {
// Stop potentially running settling animation
mRecyclerView.stopScroll();
}
addFakeMotionEvent(mFakeDragBeginTime, MotionEvent.ACTION_DOWN, 0, 0);
return true;
}
@UiThread
boolean fakeDragBy(float offsetPxFloat) {
if (!mScrollEventAdapter.isFakeDragging()) {
// Can happen legitimately if user started dragging during fakeDrag and app is still
// sending fakeDragBy commands
return false;
}
// Subtract the offset, because content scrolls in the opposite direction of finger motion
mRequestedDragDistance -= offsetPxFloat;
// Calculate amount of pixels to scroll ...
int offsetPx = Math.round(mRequestedDragDistance - mActualDraggedDistance);
// ... and keep track of pixels scrolled so we don't get rounding errors
mActualDraggedDistance += offsetPx;
long time = SystemClock.uptimeMillis();
boolean isHorizontal = mViewPager.getOrientation() == ORIENTATION_HORIZONTAL;
// Scroll deltas use pixels:
final int offsetX = isHorizontal ? offsetPx : 0;
final int offsetY = isHorizontal ? 0 : offsetPx;
// Motion events get the raw float distance:
final float x = isHorizontal ? mRequestedDragDistance : 0;
final float y = isHorizontal ? 0 : mRequestedDragDistance;
mRecyclerView.scrollBy(offsetX, offsetY);
addFakeMotionEvent(time, MotionEvent.ACTION_MOVE, x, y);
return true;
}
@UiThread
boolean endFakeDrag() {
if (!mScrollEventAdapter.isFakeDragging()) {
// Happens legitimately if user started dragging during fakeDrag
return false;
}
mScrollEventAdapter.notifyEndFakeDrag();
// Compute the velocity of the fake drag
final int pixelsPerSecond = 1000;
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(pixelsPerSecond, mMaximumVelocity);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
// And fling or snap the ViewPager2 to its destination
if (!mRecyclerView.fling(xVelocity, yVelocity)) {
// Velocity too low, trigger snap to page manually
mViewPager.snapToPage();
}
return true;
}
private void beginFakeVelocityTracker() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
final ViewConfiguration configuration = ViewConfiguration.get(mViewPager.getContext());
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
} else {
mVelocityTracker.clear();
}
}
private void addFakeMotionEvent(long time, int action, float x, float y) {
final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, action, x, y, 0);
mVelocityTracker.addMovement(ev);
ev.recycle();
}
}