Continue gestures on interaction after recents animation finishes
- Detect when start of the next task is interrupted with another gesture
(after finishing the recents animation but before the next task is
launched), and ensure that the next gesture is continued with another
other activity input consumer (but without actual remote animation
targets)
Bug: 128376812
Test: Introduce artificial delay in recents animation finish, try to quick
switch quickly
Change-Id: I057a0b2c4b7e8636467e37f5bbc8800b46c24345
This commit is contained in:
+1
-1
@@ -68,7 +68,7 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple
|
||||
* @param wasVisible true if it was visible before
|
||||
*/
|
||||
boolean onActivityReady(T activity, Boolean wasVisible) {
|
||||
activity.<RecentsView>getOverviewPanel().setCurrentTask(mTargetTaskId);
|
||||
activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTaskId);
|
||||
AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
|
||||
ActivityControlHelper.AnimationFactory factory =
|
||||
mHelper.prepareRecentsUI(activity, wasVisible,
|
||||
|
||||
+1
-1
@@ -382,7 +382,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
|
||||
mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued;
|
||||
mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher;
|
||||
if (mSwipeSharedState.canGestureBeContinued) {
|
||||
mInteractionHandler.cancelCurrentAnimation();
|
||||
mInteractionHandler.cancelCurrentAnimation(mSwipeSharedState);
|
||||
} else {
|
||||
mInteractionHandler.reset();
|
||||
}
|
||||
|
||||
@@ -59,6 +59,10 @@ public class RecentsAnimationWrapper {
|
||||
mInputProxySupplier = inputProxySupplier;
|
||||
}
|
||||
|
||||
public boolean hasTargets() {
|
||||
return targetSet != null && targetSet.hasTargets();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public synchronized void setController(SwipeAnimationTargetSet targetSet) {
|
||||
Preconditions.assertUIThread();
|
||||
|
||||
@@ -38,6 +38,8 @@ public class SwipeSharedState implements SwipeAnimationListener {
|
||||
|
||||
public boolean canGestureBeContinued;
|
||||
public boolean goingToLauncher;
|
||||
public boolean recentsAnimationFinishInterrupted;
|
||||
public int nextRunningTaskId = -1;
|
||||
|
||||
public SwipeSharedState(OverviewComponentObserver overviewComponentObserver) {
|
||||
mOverviewComponentObserver = overviewComponentObserver;
|
||||
@@ -114,9 +116,22 @@ public class SwipeSharedState implements SwipeAnimationListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a recents animation has finished, but was interrupted before the next task was
|
||||
* launched. The given {@param runningTaskId} should be used as the running task for the
|
||||
* continuing input consumer.
|
||||
*/
|
||||
public void setRecentsAnimationFinishInterrupted(int runningTaskId) {
|
||||
recentsAnimationFinishInterrupted = true;
|
||||
nextRunningTaskId = runningTaskId;
|
||||
mLastAnimationTarget = mLastAnimationTarget.cloneWithoutTargets();
|
||||
}
|
||||
|
||||
public void clearAllState() {
|
||||
clearListenerState();
|
||||
canGestureBeContinued = false;
|
||||
recentsAnimationFinishInterrupted = false;
|
||||
nextRunningTaskId = -1;
|
||||
goingToLauncher = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ public final class TaskViewUtils {
|
||||
|
||||
@Override
|
||||
public void onUpdate(float percent) {
|
||||
// TODO: Take into account the current fullscreen progress for animating the insets
|
||||
params.setProgress(1 - percent);
|
||||
RectF taskBounds = inOutHelper.applyTransform(targetSet, params);
|
||||
if (!skipViewChanges) {
|
||||
|
||||
+10
-2
@@ -26,6 +26,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.app.KeyguardManager;
|
||||
import android.app.Service;
|
||||
@@ -456,7 +457,7 @@ public class TouchInteractionService extends Service implements
|
||||
return new DeviceLockedInputConsumer(this);
|
||||
}
|
||||
|
||||
RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
|
||||
final RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
|
||||
if (!useSharedState) {
|
||||
mSwipeSharedState.clearAllState();
|
||||
}
|
||||
@@ -465,8 +466,15 @@ public class TouchInteractionService extends Service implements
|
||||
mOverviewComponentObserver.getActivityControlHelper();
|
||||
|
||||
InputConsumer base;
|
||||
if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
|
||||
if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher
|
||||
&& !mSwipeSharedState.recentsAnimationFinishInterrupted) {
|
||||
base = InputConsumer.NO_OP;
|
||||
} else if (mSwipeSharedState.recentsAnimationFinishInterrupted) {
|
||||
// If the finish animation was interrupted, then continue using the other activity input
|
||||
// consumer but with the next task as the running task
|
||||
RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
|
||||
info.id = mSwipeSharedState.nextRunningTaskId;
|
||||
base = createOtherActivityInputConsumer(event, info);
|
||||
} else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
|
||||
base = OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
|
||||
} else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
|
||||
|
||||
+48
-23
@@ -256,7 +256,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
private AnimationFactory mAnimationFactory = (t) -> { };
|
||||
private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
|
||||
|
||||
private boolean mCanceled;
|
||||
private boolean mWasLauncherAlreadyVisible;
|
||||
private int mFinishingRecentsAnimationForNewTaskId = -1;
|
||||
|
||||
private boolean mPassedOverviewThreshold;
|
||||
private boolean mGestureStarted;
|
||||
@@ -412,7 +414,6 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
mRecentsAnimationWrapper.runOnInit(() ->
|
||||
mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
|
||||
});
|
||||
mRecentsView.setEnableFreeScroll(false);
|
||||
|
||||
mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
|
||||
if (mGestureEndTarget != HOME) {
|
||||
@@ -500,10 +501,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
if (mContinuingLastGesture) {
|
||||
return;
|
||||
}
|
||||
mRecentsView.setEnableDrawingLiveTile(false);
|
||||
mRecentsView.showTask(mRunningTaskId);
|
||||
mRecentsView.setRunningTaskHidden(true);
|
||||
mRecentsView.setRunningTaskIconScaledDown(true);
|
||||
mRecentsView.onGestureAnimationStart(mRunningTaskId);
|
||||
}
|
||||
|
||||
private void launcherFrameDrawn() {
|
||||
@@ -679,7 +677,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
int runningTaskIndex = mRecentsView == null ? -1 : mRecentsView.getRunningTaskIndex();
|
||||
if (runningTaskIndex >= 0) {
|
||||
for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
|
||||
if (i != runningTaskIndex) {
|
||||
if (i != runningTaskIndex || !mRecentsAnimationWrapper.hasTargets()) {
|
||||
mRecentsView.getTaskViewAt(i).setFullscreenProgress(1 - mCurrentShift.value);
|
||||
}
|
||||
}
|
||||
@@ -838,9 +836,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
Interpolator interpolator = DEACCEL;
|
||||
final boolean goingToNewTask;
|
||||
if (mRecentsView != null) {
|
||||
final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
|
||||
final int taskToLaunch = mRecentsView.getNextPage();
|
||||
goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
|
||||
if (!mRecentsAnimationWrapper.hasTargets()) {
|
||||
// If there are no running tasks, then we can assume that this is a continuation of
|
||||
// the last gesture, but after the recents animation has finished
|
||||
goingToNewTask = true;
|
||||
} else {
|
||||
final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
|
||||
final int taskToLaunch = mRecentsView.getNextPage();
|
||||
goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
|
||||
}
|
||||
} else {
|
||||
goingToNewTask = false;
|
||||
}
|
||||
@@ -1124,14 +1128,22 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */,
|
||||
true /* freezeTaskList */);
|
||||
} else {
|
||||
int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id;
|
||||
mFinishingRecentsAnimationForNewTaskId = taskId;
|
||||
mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
|
||||
mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(
|
||||
false /* animate */, true /* freezeTaskList */);
|
||||
if (!mCanceled) {
|
||||
TaskView nextTask = mRecentsView.getTaskView(taskId);
|
||||
if (nextTask != null) {
|
||||
nextTask.launchTask(false /* animate */, true /* freezeTaskList */);
|
||||
doLogGesture(NEW_TASK);
|
||||
}
|
||||
reset();
|
||||
}
|
||||
mCanceled = false;
|
||||
mFinishingRecentsAnimationForNewTaskId = -1;
|
||||
});
|
||||
}
|
||||
TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
|
||||
doLogGesture(NEW_TASK);
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
@@ -1142,12 +1154,26 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
* Cancels any running animation so that the active target can be overriden by a new swipe
|
||||
* handle (in case of quick switch).
|
||||
*/
|
||||
public void cancelCurrentAnimation() {
|
||||
public void cancelCurrentAnimation(SwipeSharedState sharedState) {
|
||||
mCanceled = true;
|
||||
mCurrentShift.cancelAnimation();
|
||||
if (mLauncherTransitionController != null && mLauncherTransitionController
|
||||
.getAnimationPlayer().isStarted()) {
|
||||
mLauncherTransitionController.getAnimationPlayer().cancel();
|
||||
}
|
||||
|
||||
if (mFinishingRecentsAnimationForNewTaskId != -1) {
|
||||
// If we are canceling mid-starting a new task, switch to the screenshot since the
|
||||
// recents animation has finished
|
||||
switchToScreenshot();
|
||||
TaskView newRunningTaskView = mRecentsView.getTaskView(
|
||||
mFinishingRecentsAnimationForNewTaskId);
|
||||
int newRunningTaskId = newRunningTaskView != null
|
||||
? newRunningTaskView.getTask().key.id
|
||||
: -1;
|
||||
mRecentsView.setCurrentTask(newRunningTaskId);
|
||||
sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidateHandler() {
|
||||
@@ -1169,11 +1195,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
mLauncherTransitionController = null;
|
||||
}
|
||||
|
||||
mRecentsView.setEnableFreeScroll(true);
|
||||
mRecentsView.setRunningTaskIconScaledDown(false);
|
||||
mRecentsView.setOnScrollChangeListener(null);
|
||||
mRecentsView.setRunningTaskHidden(false);
|
||||
mRecentsView.setEnableDrawingLiveTile(true);
|
||||
mRecentsView.onGestureAnimationEnd();
|
||||
|
||||
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
|
||||
mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
|
||||
@@ -1194,6 +1216,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
private void switchToScreenshot() {
|
||||
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
|
||||
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
|
||||
} else if (!mRecentsAnimationWrapper.hasTargets()) {
|
||||
// If there are no targets, then we don't need to capture anything
|
||||
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
|
||||
} else {
|
||||
boolean finishTransitionPosted = false;
|
||||
SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
|
||||
@@ -1241,6 +1266,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
private void finishCurrentTransitionToRecents() {
|
||||
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
|
||||
setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
|
||||
} else if (!mRecentsAnimationWrapper.hasTargets()) {
|
||||
// If there are no targets, then there is nothing to finish
|
||||
setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
|
||||
} else {
|
||||
synchronized (mRecentsAnimationWrapper) {
|
||||
mRecentsAnimationWrapper.finish(true /* toRecents */,
|
||||
@@ -1267,10 +1295,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
|
||||
}
|
||||
mActivityControlHelper.onSwipeUpComplete(mActivity);
|
||||
mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true);
|
||||
|
||||
// Animate the first icon.
|
||||
mRecentsView.animateUpRunningTaskIconScale(mLiveTileOverlay.cancelIconAnimation());
|
||||
mRecentsView.setSwipeDownShouldLaunchApp(true);
|
||||
mRecentsView.onSwipeUpAnimationSuccess();
|
||||
|
||||
RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
|
||||
|
||||
|
||||
+14
@@ -53,6 +53,20 @@ public class SwipeAnimationTargetSet extends RemoteAnimationTargetSet {
|
||||
this.mOnFinishListener = onFinishListener;
|
||||
}
|
||||
|
||||
public boolean hasTargets() {
|
||||
return unfilteredApps.length != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the target set without any actual targets. Used only when continuing a gesture after
|
||||
* the actual recents animation has finished.
|
||||
*/
|
||||
public SwipeAnimationTargetSet cloneWithoutTargets() {
|
||||
return new SwipeAnimationTargetSet(controller, new RemoteAnimationTargetCompat[0],
|
||||
homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen,
|
||||
mOnFinishListener);
|
||||
}
|
||||
|
||||
public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
|
||||
mOnFinishListener.accept(this);
|
||||
BACKGROUND_EXECUTOR.execute(() -> {
|
||||
|
||||
@@ -744,9 +744,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
public abstract void startHome();
|
||||
|
||||
public void reset() {
|
||||
setRunningTaskViewShowScreenshot(false);
|
||||
mRunningTaskId = -1;
|
||||
mRunningTaskTileHidden = false;
|
||||
setCurrentTask(-1);
|
||||
mIgnoreResetTaskId = -1;
|
||||
mTaskListChangeId = -1;
|
||||
|
||||
@@ -757,6 +755,15 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
setCurrentPage(0);
|
||||
}
|
||||
|
||||
public @Nullable TaskView getRunningTaskView() {
|
||||
return getTaskView(mRunningTaskId);
|
||||
}
|
||||
|
||||
public int getRunningTaskIndex() {
|
||||
TaskView tv = getRunningTaskView();
|
||||
return tv == null ? -1 : indexOfChild(tv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the view if anything in recents changed.
|
||||
*/
|
||||
@@ -767,14 +774,50 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the first task in the view represents {@param task} and reloads the view
|
||||
* if needed. This allows the swipe-up gesture to assume that the first tile always
|
||||
* corresponds to the correct task.
|
||||
* All subsequent calls to reload will keep the task as the first item until {@link #reset()}
|
||||
* is called.
|
||||
* Also scrolls the view to this task
|
||||
* Called when a gesture from an app is starting.
|
||||
*/
|
||||
public void showTask(int runningTaskId) {
|
||||
public void onGestureAnimationStart(int runningTaskId) {
|
||||
// This needs to be called before the other states are set since it can create the task view
|
||||
showCurrentTask(runningTaskId);
|
||||
setEnableFreeScroll(false);
|
||||
setEnableDrawingLiveTile(false);
|
||||
setRunningTaskHidden(true);
|
||||
setRunningTaskIconScaledDown(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called only when a swipe-up gesture from an app has completed. Only called after
|
||||
* {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
|
||||
*/
|
||||
public void onSwipeUpAnimationSuccess() {
|
||||
if (getRunningTaskView() != null) {
|
||||
float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get()
|
||||
? mLiveTileOverlay.cancelIconAnimation()
|
||||
: 0f;
|
||||
animateUpRunningTaskIconScale(startProgress);
|
||||
}
|
||||
setSwipeDownShouldLaunchApp(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a gesture from an app has finished.
|
||||
*/
|
||||
public void onGestureAnimationEnd() {
|
||||
setEnableFreeScroll(true);
|
||||
setEnableDrawingLiveTile(true);
|
||||
setOnScrollChangeListener(null);
|
||||
setRunningTaskViewShowScreenshot(true);
|
||||
setRunningTaskHidden(false);
|
||||
animateUpRunningTaskIconScale();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a task view (if necessary) to represent the task with the {@param runningTaskId}.
|
||||
*
|
||||
* All subsequent calls to reload will keep the task as the first item until {@link #reset()}
|
||||
* is called. Also scrolls the view to this task.
|
||||
*/
|
||||
public void showCurrentTask(int runningTaskId) {
|
||||
if (getChildCount() == 0) {
|
||||
// Add an empty view for now until the task plan is loaded and applied
|
||||
final TaskView taskView = mTaskViewPool.getView();
|
||||
@@ -789,16 +832,33 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
new ComponentName("", ""), false);
|
||||
taskView.bind(mTmpRunningTask);
|
||||
}
|
||||
|
||||
boolean runningTaskTileHidden = mRunningTaskTileHidden;
|
||||
setCurrentTask(runningTaskId);
|
||||
setCurrentPage(getRunningTaskIndex());
|
||||
setRunningTaskViewShowScreenshot(false);
|
||||
setRunningTaskHidden(runningTaskTileHidden);
|
||||
|
||||
// Reload the task list
|
||||
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
|
||||
}
|
||||
|
||||
public @Nullable TaskView getRunningTaskView() {
|
||||
return getTaskView(mRunningTaskId);
|
||||
}
|
||||
/**
|
||||
* Sets the running task id, cleaning up the old running task if necessary.
|
||||
* @param runningTaskId
|
||||
*/
|
||||
public void setCurrentTask(int runningTaskId) {
|
||||
if (mRunningTaskId == runningTaskId) {
|
||||
return;
|
||||
}
|
||||
|
||||
public int getRunningTaskIndex() {
|
||||
TaskView tv = getRunningTaskView();
|
||||
return tv == null ? -1 : indexOfChild(tv);
|
||||
if (mRunningTaskId != -1) {
|
||||
// Reset the state on the old running task view
|
||||
setRunningTaskIconScaledDown(false);
|
||||
setRunningTaskViewShowScreenshot(true);
|
||||
setRunningTaskHidden(false);
|
||||
}
|
||||
mRunningTaskId = runningTaskId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -812,27 +872,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #showTask(int)} but does not put any restrictions on the first tile.
|
||||
*/
|
||||
public void setCurrentTask(int runningTaskId) {
|
||||
boolean runningTaskTileHidden = mRunningTaskTileHidden;
|
||||
boolean runningTaskIconScaledDown = mRunningTaskIconScaledDown;
|
||||
|
||||
setRunningTaskIconScaledDown(false);
|
||||
setRunningTaskHidden(false);
|
||||
setRunningTaskViewShowScreenshot(true);
|
||||
mRunningTaskId = runningTaskId;
|
||||
setRunningTaskViewShowScreenshot(false);
|
||||
setRunningTaskIconScaledDown(runningTaskIconScaledDown);
|
||||
setRunningTaskHidden(runningTaskTileHidden);
|
||||
|
||||
setCurrentPage(getRunningTaskIndex());
|
||||
|
||||
// Load the tasks (if the loading is already
|
||||
mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
|
||||
}
|
||||
|
||||
private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
|
||||
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
|
||||
TaskView runningTaskView = getRunningTaskView();
|
||||
@@ -1520,7 +1559,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||
public abstract boolean shouldUseMultiWindowTaskSizeStrategy();
|
||||
|
||||
protected void onTaskLaunched(boolean success) {
|
||||
resetTaskVisuals();
|
||||
if (success) {
|
||||
resetTaskVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1072,6 +1072,10 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
|
||||
|
||||
|
||||
public void setEnableFreeScroll(boolean freeScroll) {
|
||||
if (mFreeScroll == freeScroll) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean wasFreeScroll = mFreeScroll;
|
||||
mFreeScroll = freeScroll;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user