Merge "Pull up all apps interaction First phase implementation: dragging and animation interaction is implemented namely in two classes. ScrollGestureDetector and AllAppsTransitionController." into ub-launcher3-calgary
This commit is contained in:
committed by
Android (Google) Code Review
commit
63741779e5
@@ -24,6 +24,9 @@ import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.android.launcher3.allapps.AllAppsContainerView;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
|
||||
/**
|
||||
* A base container view, which supports resizing.
|
||||
*/
|
||||
@@ -48,7 +51,11 @@ public abstract class BaseContainerView extends FrameLayout {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
int width = ((Launcher) context).getDeviceProfile().availableWidthPx;
|
||||
mHorizontalPadding = DeviceProfile.getContainerPadding(context, width);
|
||||
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && (this instanceof AllAppsContainerView)) {
|
||||
mHorizontalPadding = 0;
|
||||
} else {
|
||||
mHorizontalPadding = DeviceProfile.getContainerPadding(context, width);
|
||||
}
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.BaseContainerView, defStyleAttr, 0);
|
||||
|
||||
@@ -9,6 +9,9 @@ import android.view.ViewDebug;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.android.launcher3.allapps.AllAppsContainerView;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
|
||||
public class InsettableFrameLayout extends FrameLayout implements
|
||||
ViewGroup.OnHierarchyChangeListener, Insettable {
|
||||
|
||||
@@ -31,6 +34,9 @@ public class InsettableFrameLayout extends FrameLayout implements
|
||||
lp.rightMargin += (newInsets.right - oldInsets.right);
|
||||
lp.bottomMargin += (newInsets.bottom - oldInsets.bottom);
|
||||
}
|
||||
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && child instanceof AllAppsContainerView) {
|
||||
lp.setMargins(0, 0, 0, lp.bottomMargin);
|
||||
}
|
||||
child.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ import android.widget.Toast;
|
||||
|
||||
import com.android.launcher3.DropTarget.DragObject;
|
||||
import com.android.launcher3.allapps.AllAppsContainerView;
|
||||
import com.android.launcher3.allapps.AllAppsTransitionController;
|
||||
import com.android.launcher3.allapps.DefaultAppSearchController;
|
||||
import com.android.launcher3.compat.AppWidgetManagerCompat;
|
||||
import com.android.launcher3.compat.LauncherActivityInfoCompat;
|
||||
@@ -256,6 +257,7 @@ public class Launcher extends Activity
|
||||
|
||||
// Main container view for the all apps screen.
|
||||
@Thunk AllAppsContainerView mAppsView;
|
||||
AllAppsTransitionController mAllAppsController;
|
||||
|
||||
// Main container view and the model for the widget tray screen.
|
||||
@Thunk WidgetsContainerView mWidgetsView;
|
||||
@@ -413,7 +415,8 @@ public class Launcher extends Activity
|
||||
mIconCache = app.getIconCache();
|
||||
|
||||
mDragController = new DragController(this);
|
||||
mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);
|
||||
mAllAppsController = new AllAppsTransitionController(this);
|
||||
mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, mAllAppsController);
|
||||
|
||||
mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
|
||||
|
||||
@@ -1339,8 +1342,6 @@ public class Launcher extends Activity
|
||||
* Finds all the views we need and configure them properly.
|
||||
*/
|
||||
private void setupViews() {
|
||||
final DragController dragController = mDragController;
|
||||
|
||||
mLauncherView = findViewById(R.id.launcher);
|
||||
mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
|
||||
mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
|
||||
@@ -1353,7 +1354,8 @@ public class Launcher extends Activity
|
||||
mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
|
||||
|
||||
// Setup the drag layer
|
||||
mDragLayer.setup(this, dragController);
|
||||
|
||||
mDragLayer.setup(this, mDragController, mAllAppsController);
|
||||
|
||||
// Setup the hotseat
|
||||
mHotseat = (Hotseat) findViewById(R.id.hotseat);
|
||||
@@ -1367,9 +1369,9 @@ public class Launcher extends Activity
|
||||
// Setup the workspace
|
||||
mWorkspace.setHapticFeedbackEnabled(false);
|
||||
mWorkspace.setOnLongClickListener(this);
|
||||
mWorkspace.setup(dragController);
|
||||
mWorkspace.setup(mDragController);
|
||||
mWorkspace.bindAndInitFirstWorkspaceScreen(null /* recycled qsb */);
|
||||
dragController.addDragListener(mWorkspace);
|
||||
mDragController.addDragListener(mWorkspace);
|
||||
|
||||
// Get the search/delete/uninstall bar
|
||||
mSearchDropTargetBar = (SearchDropTargetBar)
|
||||
@@ -1388,15 +1390,15 @@ public class Launcher extends Activity
|
||||
}
|
||||
|
||||
// Setup the drag controller (drop targets have to be added in reverse order in priority)
|
||||
dragController.setDragScoller(mWorkspace);
|
||||
dragController.setScrollView(mDragLayer);
|
||||
dragController.setMoveTarget(mWorkspace);
|
||||
dragController.addDropTarget(mWorkspace);
|
||||
mDragController.setDragScoller(mWorkspace);
|
||||
mDragController.setScrollView(mDragLayer);
|
||||
mDragController.setMoveTarget(mWorkspace);
|
||||
mDragController.addDropTarget(mWorkspace);
|
||||
if (mSearchDropTargetBar != null) {
|
||||
mSearchDropTargetBar.setup(this, dragController);
|
||||
mSearchDropTargetBar.setup(this, mDragController);
|
||||
}
|
||||
if (mAppInfoDropTargetBar != null) {
|
||||
mAppInfoDropTargetBar.setup(this, dragController);
|
||||
mAppInfoDropTargetBar.setup(this, mDragController);
|
||||
}
|
||||
|
||||
if (TestingUtils.MEMORY_DUMP_ENABLED) {
|
||||
@@ -1940,6 +1942,7 @@ public class Launcher extends Activity
|
||||
if (mWorkspace.getChildCount() > 0) {
|
||||
outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
|
||||
mWorkspace.getCurrentPageOffsetFromCustomContent());
|
||||
|
||||
}
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
@@ -3163,6 +3166,8 @@ public class Launcher extends Activity
|
||||
mWorkspace.startReordering(v);
|
||||
} else {
|
||||
showOverviewMode(true);
|
||||
mHotseat.requestDisallowInterceptTouchEvent(true);
|
||||
|
||||
}
|
||||
} else {
|
||||
final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
|
||||
@@ -3318,7 +3323,7 @@ public class Launcher extends Activity
|
||||
/**
|
||||
* Shows the apps view.
|
||||
*/
|
||||
void showAppsView(boolean animated, boolean resetListToTop, boolean updatePredictedApps,
|
||||
public void showAppsView(boolean animated, boolean resetListToTop, boolean updatePredictedApps,
|
||||
boolean focusSearchBar) {
|
||||
if (resetListToTop) {
|
||||
mAppsView.scrollToTop();
|
||||
|
||||
@@ -33,6 +33,8 @@ import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
|
||||
import com.android.launcher3.allapps.AllAppsContainerView;
|
||||
import com.android.launcher3.allapps.AllAppsTransitionController;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
import com.android.launcher3.util.UiThreadCircularReveal;
|
||||
import com.android.launcher3.widget.WidgetsContainerView;
|
||||
@@ -82,6 +84,17 @@ import java.util.HashMap;
|
||||
*/
|
||||
public class LauncherStateTransitionAnimation {
|
||||
|
||||
/**
|
||||
* animation used for all apps and widget tray when
|
||||
*{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code false}
|
||||
*/
|
||||
public static final int CIRCULAR_REVEAL = 0;
|
||||
/**
|
||||
* animation used for all apps and not widget tray when
|
||||
*{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code true}
|
||||
*/
|
||||
public static final int PULLUP = 1;
|
||||
|
||||
private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f;
|
||||
|
||||
/**
|
||||
@@ -113,9 +126,11 @@ public class LauncherStateTransitionAnimation {
|
||||
|
||||
@Thunk Launcher mLauncher;
|
||||
@Thunk AnimatorSet mCurrentAnimation;
|
||||
AllAppsTransitionController mAllAppsController;
|
||||
|
||||
public LauncherStateTransitionAnimation(Launcher l) {
|
||||
public LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController) {
|
||||
mLauncher = l;
|
||||
mAllAppsController = allAppsController;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,9 +169,13 @@ public class LauncherStateTransitionAnimation {
|
||||
}
|
||||
}
|
||||
};
|
||||
int animType = CIRCULAR_REVEAL;
|
||||
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
|
||||
animType = PULLUP;
|
||||
}
|
||||
// Only animate the search bar if animating from spring loaded mode back to all apps
|
||||
mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
|
||||
Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, cb);
|
||||
Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,7 +186,7 @@ public class LauncherStateTransitionAnimation {
|
||||
final WidgetsContainerView toView = mLauncher.getWidgetsView();
|
||||
final View buttonView = mLauncher.getWidgetsButton();
|
||||
mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
|
||||
Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated,
|
||||
Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL,
|
||||
new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){
|
||||
@Override
|
||||
void onTransitionComplete() {
|
||||
@@ -189,8 +208,12 @@ public class LauncherStateTransitionAnimation {
|
||||
}
|
||||
|
||||
if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
|
||||
int animType = CIRCULAR_REVEAL;
|
||||
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
|
||||
animType = PULLUP;
|
||||
}
|
||||
startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
|
||||
animated, onCompleteRunnable);
|
||||
animated, animType, onCompleteRunnable);
|
||||
} else if (fromState == Launcher.State.WIDGETS ||
|
||||
fromState == Launcher.State.WIDGETS_SPRING_LOADED) {
|
||||
startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
|
||||
@@ -208,7 +231,7 @@ public class LauncherStateTransitionAnimation {
|
||||
private AnimatorSet startAnimationToOverlay(
|
||||
final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
|
||||
final View buttonView, final BaseContainerView toView,
|
||||
final boolean animated, final PrivateTransitionCallbacks pCb) {
|
||||
final boolean animated, int animType, final PrivateTransitionCallbacks pCb) {
|
||||
final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
|
||||
final Resources res = mLauncher.getResources();
|
||||
final boolean material = Utilities.ATLEAST_LOLLIPOP;
|
||||
@@ -225,12 +248,35 @@ public class LauncherStateTransitionAnimation {
|
||||
// Cancel the current animation
|
||||
cancelAnimation();
|
||||
|
||||
playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
|
||||
animated, initialized, animation, revealDuration, layerViews);
|
||||
|
||||
if (!FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
|
||||
playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
|
||||
animated, initialized, animation, revealDuration, layerViews);
|
||||
}
|
||||
final View contentView = toView.getContentView();
|
||||
|
||||
if (animated && initialized) {
|
||||
if (!animated || !initialized) {
|
||||
toView.setTranslationX(0.0f);
|
||||
toView.setTranslationY(0.0f);
|
||||
toView.setScaleX(1.0f);
|
||||
toView.setScaleY(1.0f);
|
||||
toView.setAlpha(1.0f);
|
||||
toView.setVisibility(View.VISIBLE);
|
||||
toView.bringToFront();
|
||||
|
||||
// Show the content view
|
||||
contentView.setVisibility(View.VISIBLE);
|
||||
|
||||
dispatchOnLauncherTransitionPrepare(fromView, animated, false);
|
||||
dispatchOnLauncherTransitionStart(fromView, animated, false);
|
||||
dispatchOnLauncherTransitionEnd(fromView, animated, false);
|
||||
dispatchOnLauncherTransitionPrepare(toView, animated, false);
|
||||
dispatchOnLauncherTransitionStart(toView, animated, false);
|
||||
dispatchOnLauncherTransitionEnd(toView, animated, false);
|
||||
pCb.onTransitionComplete();
|
||||
|
||||
return null;
|
||||
}
|
||||
if (animType == CIRCULAR_REVEAL) {
|
||||
// Setup the reveal view animation
|
||||
final View revealView = toView.getRevealView();
|
||||
|
||||
@@ -366,27 +412,40 @@ public class LauncherStateTransitionAnimation {
|
||||
toView.post(startAnimRunnable);
|
||||
|
||||
return animation;
|
||||
} else {
|
||||
toView.setTranslationX(0.0f);
|
||||
toView.setTranslationY(0.0f);
|
||||
toView.setScaleX(1.0f);
|
||||
toView.setScaleY(1.0f);
|
||||
toView.setVisibility(View.VISIBLE);
|
||||
toView.bringToFront();
|
||||
} else if (animType == PULLUP) {
|
||||
animation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
dispatchOnLauncherTransitionEnd(fromView, animated, false);
|
||||
dispatchOnLauncherTransitionEnd(toView, animated, false);
|
||||
cleanupAnimation();
|
||||
pCb.onTransitionComplete();
|
||||
}
|
||||
|
||||
// Show the content view
|
||||
contentView.setVisibility(View.VISIBLE);
|
||||
});
|
||||
mAllAppsController.animateToAllApps(animation);
|
||||
|
||||
dispatchOnLauncherTransitionPrepare(fromView, animated, false);
|
||||
dispatchOnLauncherTransitionStart(fromView, animated, false);
|
||||
dispatchOnLauncherTransitionEnd(fromView, animated, false);
|
||||
dispatchOnLauncherTransitionPrepare(toView, animated, false);
|
||||
dispatchOnLauncherTransitionStart(toView, animated, false);
|
||||
dispatchOnLauncherTransitionEnd(toView, animated, false);
|
||||
pCb.onTransitionComplete();
|
||||
|
||||
return null;
|
||||
final AnimatorSet stateAnimation = animation;
|
||||
final Runnable startAnimRunnable = new Runnable() {
|
||||
public void run() {
|
||||
// Check that mCurrentAnimation hasn't changed while
|
||||
// we waited for a layout/draw pass
|
||||
if (mCurrentAnimation != stateAnimation)
|
||||
return;
|
||||
dispatchOnLauncherTransitionStart(fromView, animated, false);
|
||||
dispatchOnLauncherTransitionStart(toView, animated, false);
|
||||
|
||||
toView.requestFocus();
|
||||
stateAnimation.start();
|
||||
}
|
||||
};
|
||||
toView.post(startAnimRunnable);
|
||||
return animation;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -439,7 +498,7 @@ public class LauncherStateTransitionAnimation {
|
||||
* Starts an animation to the workspace from the apps view.
|
||||
*/
|
||||
private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
|
||||
final Workspace.State toWorkspaceState, final boolean animated,
|
||||
final Workspace.State toWorkspaceState, final boolean animated, int type,
|
||||
final Runnable onCompleteRunnable) {
|
||||
AllAppsContainerView appsView = mLauncher.getAppsView();
|
||||
// No alpha anim from all apps
|
||||
@@ -476,7 +535,7 @@ public class LauncherStateTransitionAnimation {
|
||||
// Only animate the search bar if animating to spring loaded mode from all apps
|
||||
mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
|
||||
mLauncher.getAllAppsButton(), appsView,
|
||||
animated, onCompleteRunnable, cb);
|
||||
animated, type, onCompleteRunnable, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -506,7 +565,7 @@ public class LauncherStateTransitionAnimation {
|
||||
mCurrentAnimation = startAnimationToWorkspaceFromOverlay(
|
||||
fromWorkspaceState, toWorkspaceState,
|
||||
mLauncher.getWidgetsButton(), widgetsView,
|
||||
animated, onCompleteRunnable, cb);
|
||||
animated, CIRCULAR_REVEAL, onCompleteRunnable, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -598,7 +657,7 @@ public class LauncherStateTransitionAnimation {
|
||||
private AnimatorSet startAnimationToWorkspaceFromOverlay(
|
||||
final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
|
||||
final View buttonView, final BaseContainerView fromView,
|
||||
final boolean animated, final Runnable onCompleteRunnable,
|
||||
final boolean animated, int animType, final Runnable onCompleteRunnable,
|
||||
final PrivateTransitionCallbacks pCb) {
|
||||
final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
|
||||
final Resources res = mLauncher.getResources();
|
||||
@@ -619,10 +678,29 @@ public class LauncherStateTransitionAnimation {
|
||||
|
||||
boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
|
||||
|
||||
playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
|
||||
animated, initialized, animation, revealDuration, layerViews);
|
||||
if (!FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
|
||||
playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
|
||||
animated, initialized, animation, revealDuration, layerViews);
|
||||
}
|
||||
if (!animated || !initialized) {
|
||||
mAllAppsController.finishPullDown();
|
||||
fromView.setVisibility(View.GONE);
|
||||
dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
|
||||
dispatchOnLauncherTransitionStart(fromView, animated, true);
|
||||
dispatchOnLauncherTransitionEnd(fromView, animated, true);
|
||||
dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
|
||||
dispatchOnLauncherTransitionStart(toView, animated, true);
|
||||
dispatchOnLauncherTransitionEnd(toView, animated, true);
|
||||
pCb.onTransitionComplete();
|
||||
|
||||
if (animated && initialized) {
|
||||
// Run any queued runnables
|
||||
if (onCompleteRunnable != null) {
|
||||
onCompleteRunnable.run();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
if (animType == CIRCULAR_REVEAL) {
|
||||
final View revealView = fromView.getRevealView();
|
||||
final View contentView = fromView.getContentView();
|
||||
|
||||
@@ -791,23 +869,59 @@ public class LauncherStateTransitionAnimation {
|
||||
fromView.post(startAnimRunnable);
|
||||
|
||||
return animation;
|
||||
} else /* if (!(animated && initialized)) */ {
|
||||
fromView.setVisibility(View.GONE);
|
||||
dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
|
||||
dispatchOnLauncherTransitionStart(fromView, animated, true);
|
||||
dispatchOnLauncherTransitionEnd(fromView, animated, true);
|
||||
dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
|
||||
dispatchOnLauncherTransitionStart(toView, animated, true);
|
||||
dispatchOnLauncherTransitionEnd(toView, animated, true);
|
||||
pCb.onTransitionComplete();
|
||||
} else if (animType == PULLUP) {
|
||||
animation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
dispatchOnLauncherTransitionEnd(fromView, animated, false);
|
||||
dispatchOnLauncherTransitionEnd(toView, animated, false);
|
||||
cleanupAnimation();
|
||||
pCb.onTransitionComplete();
|
||||
}
|
||||
|
||||
// Run any queued runnables
|
||||
if (onCompleteRunnable != null) {
|
||||
onCompleteRunnable.run();
|
||||
}
|
||||
});
|
||||
mAllAppsController.animateToWorkspace(animation);
|
||||
|
||||
return null;
|
||||
// Dispatch the prepare transition signal
|
||||
dispatchOnLauncherTransitionPrepare(fromView, animated, false);
|
||||
dispatchOnLauncherTransitionPrepare(toView, animated, false);
|
||||
|
||||
animation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
dispatchOnLauncherTransitionEnd(fromView, animated, true);
|
||||
dispatchOnLauncherTransitionEnd(toView, animated, true);
|
||||
|
||||
// Run any queued runnables
|
||||
if (onCompleteRunnable != null) {
|
||||
onCompleteRunnable.run();
|
||||
}
|
||||
|
||||
// This can hold unnecessary references to views.
|
||||
cleanupAnimation();
|
||||
pCb.onTransitionComplete();
|
||||
}
|
||||
});
|
||||
|
||||
final AnimatorSet stateAnimation = animation;
|
||||
final Runnable startAnimRunnable = new Runnable() {
|
||||
public void run() {
|
||||
// Check that mCurrentAnimation hasn't changed while
|
||||
// we waited for a layout/draw pass
|
||||
if (mCurrentAnimation != stateAnimation)
|
||||
return;
|
||||
dispatchOnLauncherTransitionStart(fromView, animated, false);
|
||||
dispatchOnLauncherTransitionStart(toView, animated, false);
|
||||
|
||||
// Focus the new view
|
||||
toView.requestFocus();
|
||||
stateAnimation.start();
|
||||
}
|
||||
};
|
||||
fromView.post(startAnimRunnable);
|
||||
return animation;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,8 @@ import android.content.Context;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
|
||||
import com.android.launcher3.util.TouchController;
|
||||
|
||||
/**
|
||||
* Detects pinches and animates the Workspace to/from overview mode.
|
||||
*
|
||||
@@ -30,7 +32,8 @@ import android.view.ScaleGestureDetector;
|
||||
* @see PinchThresholdManager
|
||||
* @see PinchAnimationManager
|
||||
*/
|
||||
public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
|
||||
public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
|
||||
implements TouchController {
|
||||
private static final float OVERVIEW_PROGRESS = 0f;
|
||||
private static final float WORKSPACE_PROGRESS = 1f;
|
||||
/**
|
||||
@@ -63,15 +66,16 @@ public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleG
|
||||
return mPinchStarted;
|
||||
}
|
||||
|
||||
public void onTouchEvent(MotionEvent ev) {
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (mPinchStarted) {
|
||||
if (ev.getPointerCount() > 2) {
|
||||
// Using more than two fingers causes weird behavior, so just cancel the pinch.
|
||||
cancelPinch(mPreviousProgress, -1);
|
||||
} else {
|
||||
mPinchDetector.onTouchEvent(ev);
|
||||
return mPinchDetector.onTouchEvent(ev);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,16 +20,20 @@ import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Selection;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.method.TextKeyListener;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.android.launcher3.AppInfo;
|
||||
import com.android.launcher3.BaseContainerView;
|
||||
@@ -40,6 +44,7 @@ import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.DragSource;
|
||||
import com.android.launcher3.DropTarget;
|
||||
import com.android.launcher3.ExtendedEditText;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.folder.Folder;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.Launcher;
|
||||
@@ -178,9 +183,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||
mApps.setAdapter(mAdapter);
|
||||
mLayoutManager = mAdapter.getLayoutManager();
|
||||
mItemDecoration = mAdapter.getItemDecoration();
|
||||
mRecyclerViewTopBottomPadding =
|
||||
res.getDimensionPixelSize(R.dimen.all_apps_list_top_bottom_padding);
|
||||
|
||||
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
|
||||
mRecyclerViewTopBottomPadding = 0;
|
||||
setPadding(0, 0, 0, 0);
|
||||
} else {
|
||||
mRecyclerViewTopBottomPadding =
|
||||
res.getDimensionPixelSize(R.dimen.all_apps_list_top_bottom_padding);
|
||||
}
|
||||
mSearchQueryBuilder = new SpannableStringBuilder();
|
||||
Selection.setSelection(mSearchQueryBuilder, 0);
|
||||
}
|
||||
@@ -220,6 +229,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||
mApps.removeApps(apps);
|
||||
}
|
||||
|
||||
public void setSearchBarVisible(boolean visible) {
|
||||
if (visible) {
|
||||
mSearchBarController.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mSearchBarController.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sets the search bar that shows above the a-z list.
|
||||
*/
|
||||
@@ -239,6 +255,10 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||
mAppsRecyclerView.scrollToTop();
|
||||
}
|
||||
|
||||
public boolean isScrollAtTop() {
|
||||
return ((LinearLayoutManager) mAppsRecyclerView.getLayoutManager())
|
||||
.findFirstVisibleItemPosition() == 1;
|
||||
}
|
||||
/**
|
||||
* Focuses the search field and begins an app search.
|
||||
*/
|
||||
@@ -321,13 +341,33 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||
MeasureSpec.getSize(widthMeasureSpec) - mHorizontalPadding,
|
||||
MeasureSpec.getSize(heightMeasureSpec));
|
||||
|
||||
DeviceProfile grid = mLauncher.getDeviceProfile();
|
||||
int availableWidth = (!mContentBounds.isEmpty() ? mContentBounds.width() :
|
||||
MeasureSpec.getSize(widthMeasureSpec))
|
||||
- 2 * mAppsRecyclerView.getMaxScrollbarWidth();
|
||||
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
|
||||
if (mNumAppsPerRow != grid.inv.numColumns ||
|
||||
mNumPredictedAppsPerRow != grid.inv.numColumns) {
|
||||
mNumAppsPerRow = grid.inv.numColumns;
|
||||
mNumPredictedAppsPerRow = grid.inv.numColumns;
|
||||
|
||||
mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
|
||||
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
|
||||
mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, new FullMergeAlgorithm());
|
||||
if (mNumAppsPerRow > 0) {
|
||||
int iconSize = availableWidth / mNumAppsPerRow;
|
||||
int iconSpacing = (iconSize - grid.allAppsIconSizePx) / 2;
|
||||
}
|
||||
}
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
|
||||
|
||||
// Update the number of items in the grid before we measure the view
|
||||
// TODO: mSectionNamesMargin is currently 0, but also account for it,
|
||||
// if it's enabled in the future.
|
||||
int availableWidth = (!mContentBounds.isEmpty() ? mContentBounds.width() :
|
||||
MeasureSpec.getSize(widthMeasureSpec))
|
||||
- 2 * mAppsRecyclerView.getMaxScrollbarWidth();
|
||||
DeviceProfile grid = mLauncher.getDeviceProfile();
|
||||
grid.updateAppsViewNumCols(getResources(), availableWidth);
|
||||
if (mNumAppsPerRow != grid.allAppsNumCols ||
|
||||
mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
|
||||
@@ -346,6 +386,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
|
||||
mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
|
||||
|
||||
// TODO: should we not do all this complicated computation but just match the
|
||||
// numAppsPerRow with the workspace?
|
||||
if (mNumAppsPerRow > 0) {
|
||||
int iconSize = availableWidth / mNumAppsPerRow;
|
||||
int iconSpacing = (iconSize - grid.allAppsIconSizePx) / 2;
|
||||
@@ -353,6 +395,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||
}
|
||||
}
|
||||
|
||||
// --- remove END when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
@@ -385,6 +428,24 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||
MarginLayoutParams lp = (MarginLayoutParams) mSearchContainer.getLayoutParams();
|
||||
lp.leftMargin = bgPadding.left;
|
||||
lp.rightMargin = bgPadding.right;
|
||||
|
||||
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
|
||||
MarginLayoutParams mlp = (MarginLayoutParams) mAppsRecyclerView.getLayoutParams();
|
||||
|
||||
int navBarHeight = 84; /* replace with mInset.height() in dragLayer */
|
||||
DeviceProfile grid = mLauncher.getDeviceProfile();
|
||||
int height = navBarHeight + grid.hotseatCellHeightPx;
|
||||
|
||||
mlp.topMargin = height;
|
||||
mAppsRecyclerView.setLayoutParams(mlp);
|
||||
|
||||
LinearLayout.LayoutParams llp =
|
||||
(LinearLayout.LayoutParams) mSearchInput.getLayoutParams();
|
||||
llp.topMargin = navBarHeight;
|
||||
mSearchInput.setLayoutParams(llp);
|
||||
|
||||
lp.height = height;
|
||||
}
|
||||
mSearchContainer.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
@@ -437,6 +498,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
|
||||
// Return early if this is not initiated from a touch
|
||||
if (!v.isInTouchMode()) return false;
|
||||
// When we have exited all apps or are in transition, disregard long clicks
|
||||
|
||||
if (!mLauncher.isAppsViewVisible() ||
|
||||
mLauncher.getWorkspace().isSwitchingState()) return false;
|
||||
// Return if global dragging is not enabled
|
||||
|
||||
@@ -49,6 +49,9 @@ public abstract class AllAppsSearchBarController
|
||||
protected DefaultAppSearchAlgorithm mSearchAlgorithm;
|
||||
protected InputMethodManager mInputMethodManager;
|
||||
|
||||
public void setVisibility(int visibility) {
|
||||
mInput.setVisibility(visibility);
|
||||
}
|
||||
/**
|
||||
* Sets the references to the apps model and the search result callback.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
package com.android.launcher3.allapps;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.android.launcher3.Hotseat;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAnimUtils;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
|
||||
/**
|
||||
* Handles AllApps view transition.
|
||||
* 1) Slides all apps view using direct manipulation
|
||||
* 2) When finger is released, animate to either top or bottom accordingly.
|
||||
*
|
||||
* Algorithm:
|
||||
* If release velocity > THRES1, snap according to the direction of movement.
|
||||
* If release velocity < THRES1, snap according to either top or bottom depending on whether it's
|
||||
* closer to top or closer to the page indicator.
|
||||
*/
|
||||
public class AllAppsTransitionController implements TouchController, VerticalPullDetector.Listener {
|
||||
|
||||
private static final String TAG = "AllAppsTrans";
|
||||
private static final boolean DBG = false;
|
||||
|
||||
private static final float ANIMATION_DURATION = 500;
|
||||
|
||||
private AllAppsContainerView mAppsView;
|
||||
private Hotseat mHotseat;
|
||||
private Drawable mHotseatBackground;
|
||||
private float mHotseatAlpha;
|
||||
private View mWorkspaceCurPage;
|
||||
|
||||
private final Launcher mLauncher;
|
||||
private final VerticalPullDetector mDetector;
|
||||
|
||||
// Animation in this class is controlled by a single variable {@link mProgressTransY}.
|
||||
// Visually, it represents top y coordinate of the all apps container. Using the
|
||||
// {@link mTranslation} as the denominator, this fraction value ranges in [0, 1].
|
||||
private float mProgressTransY; // numerator
|
||||
private float mTranslation = -1; // denominator
|
||||
|
||||
private float mAnimationDuration;
|
||||
private float mCurY;
|
||||
|
||||
private AnimatorSet mCurrentAnimation;
|
||||
|
||||
public AllAppsTransitionController(Launcher launcher) {
|
||||
mLauncher = launcher;
|
||||
mDetector = new VerticalPullDetector(launcher);
|
||||
mDetector.setListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
init();
|
||||
if (mLauncher.getWorkspace().isInOverviewMode() ||
|
||||
mLauncher.isWidgetsViewVisible()) {
|
||||
return false;
|
||||
}
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
mDetector.setAllAppsState(mLauncher.isAllAppsVisible(), mAppsView.isScrollAtTop());
|
||||
}
|
||||
mDetector.onTouchEvent(ev);
|
||||
return mDetector.mScrolling;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
return mDetector.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
private void init() {
|
||||
if (mAppsView != null) {
|
||||
return;
|
||||
}
|
||||
mAppsView = mLauncher.getAppsView();
|
||||
mHotseat = mLauncher.getHotseat();
|
||||
mWorkspaceCurPage = mLauncher.getWorkspace().getChildAt(
|
||||
mLauncher.getWorkspace().getCurrentPage());
|
||||
|
||||
if (mHotseatBackground == null) {
|
||||
mHotseatBackground = mHotseat.getBackground();
|
||||
mHotseatAlpha = mHotseatBackground.getAlpha() / 255f;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStart(boolean start) {
|
||||
cancelAnimation();
|
||||
mCurrentAnimation = LauncherAnimUtils.createAnimatorSet();
|
||||
mCurY = mAppsView.getTranslationY();
|
||||
preparePull(start);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param start {@code true} if start of new drag.
|
||||
*/
|
||||
public void preparePull(boolean start) {
|
||||
if (start) {
|
||||
if (!mLauncher.isAllAppsVisible()) {
|
||||
mHotseat.setBackground(null);
|
||||
mAppsView.setVisibility(View.VISIBLE);
|
||||
mAppsView.getContentView().setVisibility(View.VISIBLE);
|
||||
mAppsView.setAlpha(mHotseatAlpha);
|
||||
mAppsView.setSearchBarVisible(false);
|
||||
|
||||
if (mTranslation < 0) {
|
||||
mTranslation = mHotseat.getTop();
|
||||
setProgress(mTranslation);
|
||||
}
|
||||
} else {
|
||||
mLauncher.getWorkspace().setVisibility(View.VISIBLE);
|
||||
mLauncher.getWorkspace().setAlpha(1f);
|
||||
mLauncher.getWorkspace().onLauncherTransitionPrepare(mLauncher, false, false);
|
||||
mWorkspaceCurPage.setVisibility(View.VISIBLE);
|
||||
mAppsView.setSearchBarVisible(false);
|
||||
setLightStatusBar(false);
|
||||
}
|
||||
}
|
||||
mHotseat.setVisibility(View.VISIBLE);
|
||||
mHotseat.bringToFront();
|
||||
}
|
||||
|
||||
private void setLightStatusBar(boolean enable) {
|
||||
int systemUiFlags = mLauncher.getWindow().getDecorView().getSystemUiVisibility();
|
||||
if (enable) {
|
||||
mLauncher.getWindow().getDecorView().setSystemUiVisibility(systemUiFlags
|
||||
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||
|
||||
} else {
|
||||
mLauncher.getWindow().getDecorView().setSystemUiVisibility(systemUiFlags
|
||||
& ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private final Interpolator mAlphaInterpolator = new DecelerateInterpolator(.5f);
|
||||
|
||||
@Override
|
||||
public boolean onScroll(float displacement, float velocity) {
|
||||
if (mAppsView == null) {
|
||||
return false; // early termination.
|
||||
}
|
||||
if (0 <= mCurY + displacement && mCurY + displacement < mTranslation) {
|
||||
setProgress(mCurY + displacement);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param progress y value of the border between hotseat and all apps
|
||||
*/
|
||||
public void setProgress(float progress) {
|
||||
mProgressTransY = progress;
|
||||
float alpha = calcAlphaAllApps(progress);
|
||||
float workspaceHotseatAlpha = Math.max(mHotseatAlpha, 1 - alpha);
|
||||
setTransAndAlpha(mAppsView, progress, Math.max(mHotseatAlpha, alpha));
|
||||
setTransAndAlpha(mWorkspaceCurPage, -mTranslation + progress, workspaceHotseatAlpha);
|
||||
setTransAndAlpha(mHotseat, -mTranslation + progress, workspaceHotseatAlpha);
|
||||
}
|
||||
|
||||
public float getProgress() {
|
||||
return mProgressTransY;
|
||||
}
|
||||
|
||||
private float calcAlphaAllApps(float progress) {
|
||||
return mAlphaInterpolator.getInterpolation((mTranslation - progress)/mTranslation);
|
||||
}
|
||||
|
||||
private void setTransAndAlpha(View v, float transY, float alpha) {
|
||||
if (v != null) {
|
||||
v.setTranslationY(transY);
|
||||
v.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollEnd(float velocity, boolean fling) {
|
||||
if (mAppsView == null) {
|
||||
return; // early termination.
|
||||
}
|
||||
|
||||
if (fling) {
|
||||
if (velocity < 0) {
|
||||
calculateDuration(velocity, mAppsView.getTranslationY());
|
||||
showAppsView(); // Flinging in UP direction
|
||||
} else {
|
||||
calculateDuration(velocity, Math.abs(mTranslation - mAppsView.getTranslationY()));
|
||||
showWorkspace(); // Flinging in DOWN direction
|
||||
}
|
||||
// snap to top or bottom using the release velocity
|
||||
} else {
|
||||
if (mAppsView.getTranslationY() > mTranslation / 2) {
|
||||
calculateDuration(velocity, Math.abs(mTranslation - mAppsView.getTranslationY()));
|
||||
showWorkspace(); // Released in the bottom half
|
||||
} else {
|
||||
calculateDuration(velocity, Math.abs(mAppsView.getTranslationY()));
|
||||
showAppsView(); // Released in the top half
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateDuration(float velocity, float disp) {
|
||||
// TODO: make these values constants after tuning.
|
||||
float velocityDivisor = Math.max(1, 0.75f * velocity);
|
||||
float travelDistance = Math.max(0.2f, disp / mTranslation);
|
||||
mAnimationDuration = Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
|
||||
if (true) { // MERONG
|
||||
Log.d(TAG, String.format("calculateDuration=%f, v=%f, d=%f", mAnimationDuration, velocity, disp));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Depending on the current state of the launcher, either just
|
||||
* 1) animate
|
||||
* 2) animate and do all the state updates.
|
||||
*/
|
||||
private void showAppsView() {
|
||||
if (mLauncher.isAllAppsVisible()) {
|
||||
animateToAllApps(mCurrentAnimation);
|
||||
mCurrentAnimation.start();
|
||||
} else {
|
||||
mLauncher.showAppsView(true /* animated */, true /* resetListToTop */,
|
||||
true /* updatePredictedApps */, false /* focusSearchBar */);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Depending on the current state of the launcher, either just
|
||||
* 1) animate
|
||||
* 2) animate and do all the state updates.
|
||||
*/
|
||||
private void showWorkspace() {
|
||||
if (mLauncher.isAllAppsVisible()) {
|
||||
mLauncher.showWorkspace(true /* animated */);
|
||||
} else {
|
||||
animateToWorkspace(mCurrentAnimation);
|
||||
mCurrentAnimation.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void animateToAllApps(AnimatorSet animationOut) {
|
||||
if ((mAppsView = mLauncher.getAppsView()) == null || animationOut == null){
|
||||
return;
|
||||
}
|
||||
if (!mDetector.mScrolling) {
|
||||
preparePull(true);
|
||||
}
|
||||
mCurY = mAppsView.getTranslationY();
|
||||
final float fromAllAppsTop = mAppsView.getTranslationY();
|
||||
final float toAllAppsTop = 0;
|
||||
|
||||
ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
|
||||
fromAllAppsTop, toAllAppsTop);
|
||||
driftAndAlpha.setDuration((long) mAnimationDuration);
|
||||
animationOut.play(driftAndAlpha);
|
||||
|
||||
animationOut.addListener(new AnimatorListenerAdapter() {
|
||||
boolean canceled = false;
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
canceled = true;
|
||||
}
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (canceled) {
|
||||
return;
|
||||
} else {
|
||||
finishPullUp();
|
||||
cleanUpAnimation();
|
||||
mDetector.finishedScrolling();
|
||||
}
|
||||
}});
|
||||
mCurrentAnimation = animationOut;
|
||||
}
|
||||
|
||||
private void finishPullUp() {
|
||||
mAppsView.setSearchBarVisible(true);
|
||||
mHotseat.setVisibility(View.INVISIBLE);
|
||||
setProgress(0f);
|
||||
setLightStatusBar(true);
|
||||
}
|
||||
|
||||
public void animateToWorkspace(AnimatorSet animationOut) {
|
||||
if ((mAppsView = mLauncher.getAppsView()) == null || animationOut == null){
|
||||
return;
|
||||
}
|
||||
if(!mDetector.mScrolling) {
|
||||
preparePull(true);
|
||||
}
|
||||
final float fromAllAppsTop = mAppsView.getTranslationY();
|
||||
final float toAllAppsTop = mTranslation;
|
||||
|
||||
ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
|
||||
fromAllAppsTop, toAllAppsTop);
|
||||
driftAndAlpha.setDuration((long) mAnimationDuration);
|
||||
animationOut.play(driftAndAlpha);
|
||||
|
||||
animationOut.addListener(new AnimatorListenerAdapter() {
|
||||
boolean canceled = false;
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
canceled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (canceled) {
|
||||
return;
|
||||
} else {
|
||||
finishPullDown();
|
||||
cleanUpAnimation();
|
||||
mDetector.finishedScrolling();
|
||||
}
|
||||
}});
|
||||
mCurrentAnimation = animationOut;
|
||||
}
|
||||
|
||||
public void finishPullDown() {
|
||||
mAppsView.setVisibility(View.INVISIBLE);
|
||||
mHotseat.setBackground(mHotseatBackground);
|
||||
mHotseat.setVisibility(View.VISIBLE);
|
||||
setProgress(mTranslation);
|
||||
setLightStatusBar(false);
|
||||
}
|
||||
|
||||
private void cancelAnimation() {
|
||||
if (mCurrentAnimation != null) {
|
||||
mCurrentAnimation.cancel();
|
||||
mCurrentAnimation = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanUpAnimation() {
|
||||
mCurrentAnimation = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package com.android.launcher3.allapps;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
/**
|
||||
* One dimensional scroll gesture detector for all apps container pull up interaction.
|
||||
*/
|
||||
public class VerticalPullDetector {
|
||||
|
||||
private static final String TAG = "ScrollGesture";
|
||||
private static final boolean DBG = false;
|
||||
|
||||
private float mTouchSlop;
|
||||
|
||||
private boolean mAllAppsVisible;
|
||||
private boolean mAllAppsScrollAtTop;
|
||||
|
||||
/**
|
||||
* The minimum release velocity in pixels per millisecond that triggers fling..
|
||||
*/
|
||||
private static final float RELEASE_VELOCITY_PX_MS = 1.7f;
|
||||
|
||||
/**
|
||||
* The time constant used to calculate dampening in the low-pass filter of scroll velocity.
|
||||
* Cutoff frequency is set at 10 Hz.
|
||||
*/
|
||||
public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10);
|
||||
|
||||
/* Scroll state, this is set to true during dragging and animation. */
|
||||
boolean mScrolling;
|
||||
|
||||
|
||||
float mDownY;
|
||||
float mDownMillis;
|
||||
|
||||
float mLastY;
|
||||
float mLastMillis;
|
||||
|
||||
float mVelocity;
|
||||
float mLastDisplacement;
|
||||
float mDisplacement;
|
||||
|
||||
/* scroll started during previous animation */
|
||||
boolean mSubtractSlop = true;
|
||||
|
||||
/* Client of this gesture detector can register a callback. */
|
||||
Listener mListener;
|
||||
|
||||
public void setListener(Listener l) {
|
||||
mListener = l;
|
||||
}
|
||||
|
||||
interface Listener{
|
||||
/**
|
||||
* @param start when should
|
||||
*/
|
||||
void onScrollStart(boolean start);
|
||||
boolean onScroll(float displacement, float velocity);
|
||||
void onScrollEnd(float velocity, boolean fling);
|
||||
}
|
||||
|
||||
public VerticalPullDetector(Context context) {
|
||||
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
}
|
||||
|
||||
public void setAllAppsState(boolean allAppsVisible, boolean scrollAtTop) {
|
||||
mAllAppsVisible = allAppsVisible;
|
||||
mAllAppsScrollAtTop = scrollAtTop;
|
||||
}
|
||||
|
||||
private boolean shouldScrollStart() {
|
||||
if (mAllAppsVisible && mDisplacement > mTouchSlop && mAllAppsScrollAtTop) {
|
||||
return true;
|
||||
}
|
||||
if (!mAllAppsVisible && mDisplacement < -mTouchSlop) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
switch (ev.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mDownMillis = ev.getDownTime();
|
||||
mDownY = ev.getY();
|
||||
mLastDisplacement = 0;
|
||||
mVelocity = 0;
|
||||
|
||||
if (mScrolling) {
|
||||
reportScrollStart(true /* recatch */);
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
mDisplacement = computeDisplacement(ev);
|
||||
mVelocity = computeVelocity(ev, mVelocity);
|
||||
|
||||
if (!mScrolling && shouldScrollStart()) {
|
||||
mScrolling = true;
|
||||
reportScrollStart(false /* recatch */);
|
||||
}
|
||||
if (mScrolling && mListener != null) {
|
||||
reportScroll();
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
// These are synthetic events and there is no need to update internal values.
|
||||
if (mScrolling && mListener != null) {
|
||||
reportScrollEnd();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//TODO: add multi finger tracking by tracking active pointer.
|
||||
break;
|
||||
}
|
||||
// Do house keeping.
|
||||
mLastDisplacement = mDisplacement;
|
||||
|
||||
mLastY = ev.getY();
|
||||
mLastMillis = ev.getEventTime();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void finishedScrolling() {
|
||||
mScrolling = false;
|
||||
}
|
||||
|
||||
private boolean reportScrollStart(boolean recatch) {
|
||||
mListener.onScrollStart(!recatch);
|
||||
if (DBG) {
|
||||
Log.d(TAG, "onScrollStart recatch:" + recatch);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean reportScroll() {
|
||||
float delta = mDisplacement - mLastDisplacement;
|
||||
if (delta != 0) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, String.format("onScroll disp=%.1f, velocity=%.1f",
|
||||
mDisplacement, mVelocity));
|
||||
}
|
||||
return mListener.onScroll(mDisplacement - (mSubtractSlop? mTouchSlop : 0), mVelocity);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void reportScrollEnd() {
|
||||
if (DBG) {
|
||||
Log.d(TAG, String.format("onScrolEnd disp=%.1f, velocity=%.1f",
|
||||
mDisplacement, mVelocity));
|
||||
}
|
||||
mListener.onScrollEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
|
||||
}
|
||||
/**
|
||||
* Computes the damped velocity using the two motion events and the previous velocity.
|
||||
*/
|
||||
private float computeVelocity(MotionEvent to, float previousVelocity) {
|
||||
float delta = computeDelta(to);
|
||||
|
||||
float deltaTimeMillis = to.getEventTime() - mLastMillis;
|
||||
float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0;
|
||||
if (Math.abs(previousVelocity) < 0.001f) {
|
||||
return velocity;
|
||||
}
|
||||
|
||||
float alpha = computeDampeningFactor(deltaTimeMillis);
|
||||
return interpolate(previousVelocity, velocity, alpha);
|
||||
}
|
||||
|
||||
private float computeDisplacement(MotionEvent to) {
|
||||
return to.getY() - mDownY;
|
||||
}
|
||||
|
||||
private float computeDelta(MotionEvent to) {
|
||||
return to.getY() - mLastY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a time-dependent dampening factor using delta time.
|
||||
*/
|
||||
private static float computeDampeningFactor(float deltaTime) {
|
||||
return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the linear interpolation between two values
|
||||
*/
|
||||
private static float interpolate(float from, float to, float alpha) {
|
||||
return (1.0f - alpha) * from + alpha * to;
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@ import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
@@ -53,7 +54,7 @@ import java.util.HashSet;
|
||||
/**
|
||||
* Class for initiating a drag within a view or across multiple views.
|
||||
*/
|
||||
public class DragController implements DragDriver.EventListener {
|
||||
public class DragController implements DragDriver.EventListener, TouchController {
|
||||
private static final String TAG = "Launcher.DragController";
|
||||
|
||||
/** Indicates the drag is a move. */
|
||||
|
||||
@@ -43,6 +43,7 @@ import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.launcher3.AppWidgetResizeFrame;
|
||||
import com.android.launcher3.BaseContainerView;
|
||||
import com.android.launcher3.CellLayout;
|
||||
import com.android.launcher3.InsettableFrameLayout;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
@@ -56,10 +57,13 @@ import com.android.launcher3.ShortcutAndWidgetContainer;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
|
||||
import com.android.launcher3.allapps.AllAppsContainerView;
|
||||
import com.android.launcher3.allapps.AllAppsTransitionController;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.folder.Folder;
|
||||
import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -117,6 +121,11 @@ public class DragLayer extends InsettableFrameLayout {
|
||||
|
||||
// Related to pinch-to-go-to-overview gesture.
|
||||
private PinchToOverviewListener mPinchListener = null;
|
||||
|
||||
// Handles all apps pull up interaction
|
||||
private AllAppsTransitionController mAllAppsController;
|
||||
|
||||
private TouchController mActiveController;
|
||||
/**
|
||||
* Used to create a new DragLayer from XML.
|
||||
*
|
||||
@@ -138,9 +147,11 @@ public class DragLayer extends InsettableFrameLayout {
|
||||
mIsRtl = Utilities.isRtl(res);
|
||||
}
|
||||
|
||||
public void setup(Launcher launcher, DragController controller) {
|
||||
public void setup(Launcher launcher, DragController dragController,
|
||||
AllAppsTransitionController allAppsTransitionController) {
|
||||
mLauncher = launcher;
|
||||
mDragController = controller;
|
||||
mDragController = dragController;
|
||||
mAllAppsController = allAppsTransitionController;
|
||||
|
||||
boolean isAccessibilityEnabled = ((AccessibilityManager) mLauncher.getSystemService(
|
||||
Context.ACCESSIBILITY_SERVICE)).isEnabled();
|
||||
@@ -246,6 +257,7 @@ public class DragLayer extends InsettableFrameLayout {
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
int action = ev.getAction();
|
||||
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
if (handleTouchDown(ev, true)) {
|
||||
return true;
|
||||
@@ -258,11 +270,21 @@ public class DragLayer extends InsettableFrameLayout {
|
||||
}
|
||||
clearAllResizeFrames();
|
||||
|
||||
if (mPinchListener != null && mPinchListener.onInterceptTouchEvent(ev)) {
|
||||
// Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
|
||||
if (mDragController.onInterceptTouchEvent(ev)) {
|
||||
mActiveController = mDragController;
|
||||
return true;
|
||||
}
|
||||
return mDragController.onInterceptTouchEvent(ev);
|
||||
if (mAllAppsController.onInterceptTouchEvent(ev)) {
|
||||
mActiveController = mAllAppsController;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mPinchListener != null && mPinchListener.onInterceptTouchEvent(ev)) {
|
||||
// Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
|
||||
mActiveController = mPinchListener;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -375,11 +397,6 @@ public class DragLayer extends InsettableFrameLayout {
|
||||
int x = (int) ev.getX();
|
||||
int y = (int) ev.getY();
|
||||
|
||||
// This is only reached if a pinch was started from onInterceptTouchEvent();
|
||||
// this continues sending events for it.
|
||||
if (mPinchListener != null) {
|
||||
mPinchListener.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
if (handleTouchDown(ev, false)) {
|
||||
@@ -406,7 +423,10 @@ public class DragLayer extends InsettableFrameLayout {
|
||||
}
|
||||
}
|
||||
if (handled) return true;
|
||||
return mDragController.onTouchEvent(ev);
|
||||
if (mActiveController != null) {
|
||||
return mActiveController.onTouchEvent(ev);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.android.launcher3.util;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
public interface TouchController {
|
||||
boolean onTouchEvent(MotionEvent ev);
|
||||
boolean onInterceptTouchEvent(MotionEvent ev);
|
||||
}
|
||||
Reference in New Issue
Block a user