From d3ef3065ab0941567c45e9aec98783138b623c68 Mon Sep 17 00:00:00 2001 From: Michael Jurka Date: Tue, 23 Nov 2010 16:23:58 -0800 Subject: added spring loaded mode for adding items to workspace Change-Id: Ie92294fe2b1d6697d84756a2fcea91a09f72825b --- src/com/android/launcher2/Alarm.java | 76 +++++++++++ src/com/android/launcher2/AllAppsPagedView.java | 14 +- src/com/android/launcher2/CellLayout.java | 10 +- src/com/android/launcher2/CustomizePagedView.java | 38 ++++-- src/com/android/launcher2/Launcher.java | 74 ++++++++-- src/com/android/launcher2/PagedView.java | 63 +++++++-- .../launcher2/SpringLoadedDragController.java | 58 ++++++++ src/com/android/launcher2/Workspace.java | 152 +++++++++++++++------ 8 files changed, 409 insertions(+), 76 deletions(-) create mode 100644 src/com/android/launcher2/Alarm.java create mode 100644 src/com/android/launcher2/SpringLoadedDragController.java (limited to 'src/com/android/launcher2') diff --git a/src/com/android/launcher2/Alarm.java b/src/com/android/launcher2/Alarm.java new file mode 100644 index 000000000..38ff36733 --- /dev/null +++ b/src/com/android/launcher2/Alarm.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2010 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.launcher2; + +import android.os.Handler; + +public class Alarm implements Runnable{ + // if we reach this time and the alarm hasn't been cancelled, call the listener + private long mAlarmTriggerTime; + + // if we've scheduled a call to run() (ie called mHandler.postDelayed), this variable is true. + // We use this to avoid having multiple pending callbacks + private boolean mWaitingForCallback; + + private Handler mHandler; + private OnAlarmListener mAlarmListener; + + public Alarm() { + mHandler = new Handler(); + } + + public void setOnAlarmListener(OnAlarmListener alarmListener) { + mAlarmListener = alarmListener; + } + + // Sets the alarm to go off in a certain number of milliseconds. If the alarm is already set, + // it's overwritten and only the new alarm setting is used + public void setAlarm(long millisecondsInFuture) { + long currentTime = System.currentTimeMillis(); + mAlarmTriggerTime = currentTime + millisecondsInFuture; + if (!mWaitingForCallback) { + mHandler.postDelayed(this, mAlarmTriggerTime - currentTime); + mWaitingForCallback = true; + } + } + + public void cancelAlarm() { + mAlarmTriggerTime = 0; + } + + // this is called when our timer runs out + public void run() { + mWaitingForCallback = false; + if (mAlarmTriggerTime != 0) { + long currentTime = System.currentTimeMillis(); + if (mAlarmTriggerTime > currentTime) { + // We still need to wait some time to trigger spring loaded mode-- + // post a new callback + mHandler.postDelayed(this, Math.max(0, mAlarmTriggerTime - currentTime)); + mWaitingForCallback = true; + } else { + if (mAlarmListener != null) { + mAlarmListener.onAlarm(this); + } + } + } + } +} + +interface OnAlarmListener { + public void onAlarm(Alarm alarm); +} diff --git a/src/com/android/launcher2/AllAppsPagedView.java b/src/com/android/launcher2/AllAppsPagedView.java index 8d4ddba7f..2fd0b6569 100644 --- a/src/com/android/launcher2/AllAppsPagedView.java +++ b/src/com/android/launcher2/AllAppsPagedView.java @@ -21,7 +21,11 @@ import com.android.launcher.R; import android.content.ComponentName; import android.content.Context; import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.animation.AnimationUtils; @@ -258,8 +262,14 @@ public class AllAppsPagedView extends PagedView ApplicationInfo app = (ApplicationInfo) v.getTag(); app = new ApplicationInfo(app); - mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1); - mDragController.startDrag(v, this, app, DragController.DRAG_ACTION_COPY); + // get icon (top compound drawable, index is 1) + final Drawable icon = ((TextView) v).getCompoundDrawables()[1]; + Bitmap b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(b); + icon.draw(c); + mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1, b); + mDragController.startDrag(v, b, this, app, DragController.DRAG_ACTION_COPY, null); return true; } diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java index 8fe489dff..a104c554f 100644 --- a/src/com/android/launcher2/CellLayout.java +++ b/src/com/android/launcher2/CellLayout.java @@ -801,7 +801,10 @@ public class CellLayout extends ViewGroup implements Dimmable { cellXY[0] + childLeft + lp.width / 2, cellXY[1] + childTop + lp.height / 2, 0, null); - ((Workspace) mParent).animateViewIntoPosition(child); + if (lp.animateDrop) { + lp.animateDrop = false; + ((Workspace) mParent).animateViewIntoPosition(child); + } } } } @@ -1224,11 +1227,12 @@ public class CellLayout extends ViewGroup implements Dimmable { * * @param child The child that is being dropped */ - void onDropChild(View child) { + void onDropChild(View child, boolean animate) { if (child != null) { LayoutParams lp = (LayoutParams) child.getLayoutParams(); lp.isDragging = false; lp.dropped = true; + lp.animateDrop = animate; child.setVisibility(View.VISIBLE); child.requestLayout(); } @@ -1466,6 +1470,8 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { boolean dropped; + boolean animateDrop; + public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); cellHSpan = 1; diff --git a/src/com/android/launcher2/CustomizePagedView.java b/src/com/android/launcher2/CustomizePagedView.java index 62dcf4a0d..342974a4b 100644 --- a/src/com/android/launcher2/CustomizePagedView.java +++ b/src/com/android/launcher2/CustomizePagedView.java @@ -60,6 +60,7 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.TextView; import com.android.launcher.R; @@ -587,30 +588,47 @@ public class CustomizePagedView extends PagedView mIsDragging = true; switch (mCustomizationType) { - case WidgetCustomization: + case WidgetCustomization: { // Get the widget preview as the drag representation + final LinearLayout l = (LinearLayout) v; + final Drawable icon = ((ImageView) l.findViewById(R.id.widget_preview)).getDrawable(); + Bitmap b = drawableToBitmap(icon); PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) v.getTag(); final View dragView = v.findViewById(R.id.widget_preview); - mLauncher.getWorkspace().onDragStartedWithItemMinSize( - createWidgetInfo.minWidth, createWidgetInfo.minHeight); - mDragController.startDrag(dragView, this, createWidgetInfo, DragController.DRAG_ACTION_COPY, null); + int[] spanXY = CellLayout.rectToCell( + getResources(), createWidgetInfo.minWidth, createWidgetInfo.minHeight, null); + createWidgetInfo.spanX = spanXY[0]; + createWidgetInfo.spanY = spanXY[1]; + mLauncher.getWorkspace().onDragStartedWithItemSpans(spanXY[0], spanXY[1], b); + mDragController.startDrag( + v, b, this, createWidgetInfo, DragController.DRAG_ACTION_COPY, null); return true; - case ShortcutCustomization: + } + case ShortcutCustomization: { + // get icon (top compound drawable, index is 1) + final Drawable icon = ((TextView) v).getCompoundDrawables()[1]; + Bitmap b = drawableToBitmap(icon); PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag(); - mDragController.startDrag(v, this, createItemInfo, DragController.DRAG_ACTION_COPY); - mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1); + mDragController.startDrag( + v, b, this, createItemInfo, DragController.DRAG_ACTION_COPY, null); + mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1, b); return true; - case ApplicationCustomization: + } + case ApplicationCustomization: { // Pick up the application for dropping + // get icon (top compound drawable, index is 1) + final Drawable icon = ((TextView) v).getCompoundDrawables()[1]; + Bitmap b = drawableToBitmap(icon); ApplicationInfo app = (ApplicationInfo) v.getTag(); app = new ApplicationInfo(app); - mDragController.startDrag(v, this, app, DragController.DRAG_ACTION_COPY); - mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1); + mDragController.startDrag(v, b, this, app, DragController.DRAG_ACTION_COPY, null); + mLauncher.getWorkspace().onDragStartedWithItemSpans(1, 1, b); return true; } + } return false; } diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java index d5f20c548..0cb0e138e 100644 --- a/src/com/android/launcher2/Launcher.java +++ b/src/com/android/launcher2/Launcher.java @@ -176,7 +176,8 @@ public final class Launcher extends Activity private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon"; /** The different states that Launcher can be in. */ - private enum State { WORKSPACE, ALL_APPS, CUSTOMIZE, OVERVIEW }; + private enum State { WORKSPACE, ALL_APPS, CUSTOMIZE, OVERVIEW, + CUSTOMIZE_SPRING_LOADED, ALL_APPS_SPRING_LOADED }; private State mState = State.WORKSPACE; private AnimatorSet mStateAnimation; @@ -1112,7 +1113,23 @@ public final class Launcher extends Activity final int[] cellXY = mTmpAddItemCellCoordinates; final CellLayout layout = (CellLayout) mWorkspace.getChildAt(screen); - if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) { + int[] touchXY = null; + if (mAddDropPosition != null && mAddDropPosition[0] > -1 && mAddDropPosition[1] > -1) { + touchXY = mAddDropPosition; + } + boolean foundCellSpan = false; + if (touchXY != null) { + // when dragging and dropping, just find the closest free spot + CellLayout screenLayout = (CellLayout) mWorkspace.getChildAt(screen); + int[] result = screenLayout.findNearestVacantArea( + touchXY[0], touchXY[1], 1, 1, cellXY); + foundCellSpan = (result != null); + } else { + foundCellSpan = layout.findCellForSpanThatIntersects( + cellXY, 1, 1, intersectCellX, intersectCellY); + } + + if (!foundCellSpan) { showOutOfSpaceMessage(); return; } @@ -1151,15 +1168,13 @@ public final class Launcher extends Activity if (mAddDropPosition != null && mAddDropPosition[0] > -1 && mAddDropPosition[1] > -1) { touchXY = mAddDropPosition; } - boolean findNearestVacantAreaFailed = false; boolean foundCellSpan = false; if (touchXY != null) { // when dragging and dropping, just find the closest free spot CellLayout screenLayout = (CellLayout) mWorkspace.getChildAt(screen); int[] result = screenLayout.findNearestVacantArea( touchXY[0], touchXY[1], spanXY[0], spanXY[1], cellXY); - findNearestVacantAreaFailed = (result == null); - foundCellSpan = !findNearestVacantAreaFailed; + foundCellSpan = (result != null); } else { // if we long pressed on an empty cell to bring up a menu, // make sure we intersect the empty cell @@ -1626,8 +1641,6 @@ public final class Launcher extends Activity void addAppWidgetFromDrop(PendingAddWidgetInfo info, int screen, int[] position) { resetAddInfo(); mAddScreen = screen; - - // only set mAddDropPosition if we dropped on home screen in "spring-loaded" manner mAddDropPosition = position; int appWidgetId = getAppWidgetHost().allocateAppWidgetId(); @@ -2685,6 +2698,10 @@ public final class Launcher extends Activity * @param animated If true, the transition will be animated. */ private void cameraZoomIn(State fromState, boolean animated) { + cameraZoomIn(fromState, animated, false); + } + + private void cameraZoomIn(State fromState, boolean animated, boolean springLoaded) { Resources res = getResources(); int duration = res.getInteger(R.integer.config_allAppsZoomOutTime); float scaleFactor = (float) res.getInteger(R.integer.config_allAppsZoomScaleFactor); @@ -2696,7 +2713,9 @@ public final class Launcher extends Activity setPivotsForZoom(fromView, fromState, scaleFactor); - mWorkspace.unshrink(animated); + if (!springLoaded) { + mWorkspace.unshrink(animated); + } if (animated) { if (mStateAnimation != null) mStateAnimation.cancel(); @@ -2719,7 +2738,9 @@ public final class Launcher extends Activity AnimatorSet toolbarHideAnim = new AnimatorSet(); AnimatorSet toolbarShowAnim = new AnimatorSet(); - hideAndShowToolbarButtons(State.WORKSPACE, toolbarShowAnim, toolbarHideAnim); + if (!springLoaded) { + hideAndShowToolbarButtons(State.WORKSPACE, toolbarShowAnim, toolbarHideAnim); + } mStateAnimation.playTogether(scaleAnim, toolbarHideAnim, alphaAnim); @@ -2730,7 +2751,9 @@ public final class Launcher extends Activity mStateAnimation.start(); } else { fromView.setVisibility(View.GONE); - hideAndShowToolbarButtons(State.WORKSPACE, null, null); + if (!springLoaded) { + hideAndShowToolbarButtons(State.WORKSPACE, null, null); + } } } @@ -2859,6 +2882,33 @@ public final class Launcher extends Activity mState = State.WORKSPACE; } + void enterSpringLoadedDragMode(CellLayout layout) { + mWorkspace.enterSpringLoadedDragMode(layout); + if (mState == State.ALL_APPS) { + cameraZoomIn(State.ALL_APPS, true, true); + mState = State.ALL_APPS_SPRING_LOADED; + } else if (mState == State.CUSTOMIZE) { + cameraZoomIn(State.CUSTOMIZE, true, true); + mState = State.CUSTOMIZE_SPRING_LOADED; + }/* else { + // we're already in spring loaded mode; don't do anything + }*/ + } + + void exitSpringLoadedDragMode() { + if (mState == State.ALL_APPS_SPRING_LOADED) { + mWorkspace.exitSpringLoadedDragMode(Workspace.ShrinkState.BOTTOM_VISIBLE); + cameraZoomOut(State.ALL_APPS, true); + mState = State.ALL_APPS; + } else if (mState == State.CUSTOMIZE_SPRING_LOADED) { + mWorkspace.exitSpringLoadedDragMode(Workspace.ShrinkState.TOP); + cameraZoomOut(State.CUSTOMIZE, true); + mState = State.CUSTOMIZE; + }/* else { + // we're not in spring loaded mode; don't do anything + }*/ + } + /** * Things to test when changing this code. * - Home from workspace @@ -2899,7 +2949,7 @@ public final class Launcher extends Activity * - From another workspace */ void closeAllApps(boolean animated) { - if (mState == State.ALL_APPS) { + if (mState == State.ALL_APPS || mState == State.ALL_APPS_SPRING_LOADED) { mWorkspace.setVisibility(View.VISIBLE); if (LauncherApplication.isScreenXLarge()) { cameraZoomIn(State.ALL_APPS, animated); @@ -2932,7 +2982,7 @@ public final class Launcher extends Activity // Hide the customization drawer (only exists in x-large configuration) void hideCustomizationDrawer(boolean animated) { - if (mState == State.CUSTOMIZE) { + if (mState == State.CUSTOMIZE || mState == State.CUSTOMIZE_SPRING_LOADED) { cameraZoomIn(State.CUSTOMIZE, animated); } } diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java index fed0884c3..856507d64 100644 --- a/src/com/android/launcher2/PagedView.java +++ b/src/com/android/launcher2/PagedView.java @@ -110,6 +110,9 @@ public abstract class PagedView extends ViewGroup { protected boolean mAllowOverScroll = true; protected int mUnboundedScrollX; + // parameter that adjusts the layout to be optimized for CellLayouts with that scale factor + protected float mLayoutScale = 1.0f; + protected static final int INVALID_POINTER = -1; protected int mActivePointerId = INVALID_POINTER; @@ -268,7 +271,9 @@ public abstract class PagedView extends ViewGroup { if (!mScroller.isFinished()) { mScroller.abortAnimation(); } - if (getChildCount() == 0 || currentPage == mCurrentPage) { + // don't introduce any checks like mCurrentPage == currentPage here-- if we change the + // the default + if (getChildCount() == 0) { return; } @@ -445,6 +450,32 @@ public abstract class PagedView extends ViewGroup { setCurrentPage(newCurrentPage); } + // A layout scale of 1.0f assumes that the CellLayouts, in their unshrunken state, have a + // scale of 1.0f. A layout scale of 0.8f assumes the CellLayouts have a scale of 0.8f, and + // tightens the layout accordingly + public void setLayoutScale(float childrenScale) { + mLayoutScale = childrenScale; + + // Now we need to do a re-layout, but preserving absolute X and Y coordinates + int childCount = getChildCount(); + float childrenX[] = new float[childCount]; + float childrenY[] = new float[childCount]; + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + childrenX[i] = child.getX(); + childrenY[i] = child.getY(); + } + onLayout(false, mLeft, mTop, mRight, mBottom); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + child.setX(childrenX[i]); + child.setY(childrenY[i]); + } + // Also, the page offset has changed (since the pages are now smaller); + // update the page offset, but again preserving absolute X and Y coordinates + moveToNewPageWithoutMovingCellLayouts(mCurrentPage); + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { @@ -466,17 +497,21 @@ public abstract class PagedView extends ViewGroup { for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getVisibility() != View.GONE) { - final int childWidth = child.getMeasuredWidth(); + final int childWidth = getScaledMeasuredWidth(child); final int childHeight = child.getMeasuredHeight(); int childTop = mPaddingTop; if (mCenterPagesVertically) { childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2; } + child.layout(childLeft, childTop, - childLeft + childWidth, childTop + childHeight); + childLeft + child.getMeasuredWidth(), childTop + childHeight); childLeft += childWidth + mPageSpacing; } } + if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { + mFirstLayout = false; + } } protected void updateAdjacentPagesAlpha() { @@ -487,7 +522,7 @@ public abstract class PagedView extends ViewGroup { final int childCount = getChildCount(); for (int i = 0; i < childCount; ++i) { View layout = (View) getChildAt(i); - int childWidth = layout.getMeasuredWidth(); + int childWidth = getScaledMeasuredWidth(layout); int halfChildWidth = (childWidth / 2); int childCenter = getChildOffset(i) + halfChildWidth; @@ -503,11 +538,11 @@ public abstract class PagedView extends ViewGroup { int distanceFromScreenCenter = childCenter - screenCenter; if (distanceFromScreenCenter > 0) { if (i > 0) { - d += getChildAt(i - 1).getMeasuredWidth() / 2; + d += getScaledMeasuredWidth(getChildAt(i - 1)) / 2; } } else { if (i < childCount - 1) { - d += getChildAt(i + 1).getMeasuredWidth() / 2; + d += getScaledMeasuredWidth(getChildAt(i + 1)) / 2; } } d += mPageSpacing; @@ -553,7 +588,7 @@ public abstract class PagedView extends ViewGroup { // page. final int pageCount = getChildCount(); if (pageCount > 0) { - final int pageWidth = getChildAt(0).getMeasuredWidth(); + final int pageWidth = getScaledMeasuredWidth(getChildAt(0)); final int screenWidth = getMeasuredWidth(); int x = getRelativeChildOffset(0) + pageWidth; int leftScreen = 0; @@ -563,7 +598,7 @@ public abstract class PagedView extends ViewGroup { x += pageWidth + mPageSpacing; // replace above line with this if you don't assume all pages have same width as 0th // page: - // x += getChildAt(leftScreen).getMeasuredWidth(); + // x += getScaledMeasuredWidth(getChildAt(leftScreen)); } rightScreen = leftScreen; while (x < mScrollX + screenWidth) { @@ -572,7 +607,7 @@ public abstract class PagedView extends ViewGroup { // replace above line with this if you don't assume all pages have same width as 0th // page: //if (rightScreen < pageCount) { - // x += getChildAt(rightScreen).getMeasuredWidth(); + // x += getScaledMeasuredWidth(getChildAt(rightScreen)); //} } rightScreen = Math.min(getChildCount() - 1, rightScreen); @@ -1049,7 +1084,7 @@ public abstract class PagedView extends ViewGroup { int right; for (int i = 0; i < childCount; ++i) { left = getRelativeChildOffset(i); - right = (left + getChildAt(i).getMeasuredWidth()); + right = (left + getScaledMeasuredWidth(getChildAt(i))); if (left <= relativeOffset && relativeOffset <= right) { return i; } @@ -1067,11 +1102,15 @@ public abstract class PagedView extends ViewGroup { int offset = getRelativeChildOffset(0); for (int i = 0; i < index; ++i) { - offset += getChildAt(i).getMeasuredWidth() + mPageSpacing; + offset += getScaledMeasuredWidth(getChildAt(i)) + mPageSpacing; } return offset; } + protected int getScaledMeasuredWidth(View child) { + return (int) (child.getMeasuredWidth() * mLayoutScale + 0.5f); + } + int getPageNearestToCenterOfScreen() { int minDistanceFromScreenCenter = getMeasuredWidth(); int minDistanceFromScreenCenterIndex = -1; @@ -1079,7 +1118,7 @@ public abstract class PagedView extends ViewGroup { final int childCount = getChildCount(); for (int i = 0; i < childCount; ++i) { View layout = (View) getChildAt(i); - int childWidth = layout.getMeasuredWidth(); + int childWidth = getScaledMeasuredWidth(layout); int halfChildWidth = (childWidth / 2); int childCenter = getChildOffset(i) + halfChildWidth; int distanceFromScreenCenter = Math.abs(childCenter - screenCenter); diff --git a/src/com/android/launcher2/SpringLoadedDragController.java b/src/com/android/launcher2/SpringLoadedDragController.java new file mode 100644 index 000000000..a734258f9 --- /dev/null +++ b/src/com/android/launcher2/SpringLoadedDragController.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2010 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.launcher2; + +public class SpringLoadedDragController implements OnAlarmListener { + // how long the user must hover over a mini-screen before it unshrinks + final long ENTER_SPRING_LOAD_HOVER_TIME = 1000; + final long EXIT_SPRING_LOAD_HOVER_TIME = 200; + + Alarm mAlarm; + + // the screen the user is currently hovering over, if any + private CellLayout mScreen; + private Launcher mLauncher; + + public SpringLoadedDragController(Launcher launcher) { + mLauncher = launcher; + mAlarm = new Alarm(); + mAlarm.setOnAlarmListener(this); + } + + public void onDragEnter(CellLayout cl) { + mScreen = cl; + mAlarm.setAlarm(ENTER_SPRING_LOAD_HOVER_TIME); + } + + public void onDragExit() { + if (mScreen != null) { + mScreen.onDragExit(); + } + mScreen = null; + mAlarm.setAlarm(EXIT_SPRING_LOAD_HOVER_TIME); + } + + // this is called when our timer runs out + public void onAlarm(Alarm alarm) { + if (mScreen != null) { + // we're currently hovering over a screen + mLauncher.enterSpringLoadedDragMode(mScreen); + } else { + mLauncher.exitSpringLoadedDragMode(); + } + } +} diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java index 239f6f626..b58faed94 100644 --- a/src/com/android/launcher2/Workspace.java +++ b/src/com/android/launcher2/Workspace.java @@ -81,6 +81,9 @@ public class Workspace extends SmoothPagedView // customization mode private static final float SHRINK_FACTOR = 0.16f; + // How much the screens shrink when we enter spring loaded drag mode + private static final float SPRING_LOADED_DRAG_SHRINK_FACTOR = 0.7f; + // Y rotation to apply to the workspace screens private static final float WORKSPACE_ROTATION = 12.5f; private static final float WORKSPACE_TRANSLATION = 50.0f; @@ -118,6 +121,7 @@ public class Workspace extends SmoothPagedView private int mDefaultPage; private boolean mPageMoving = false; + private boolean mIsDragInProcess = false; /** * CellInfo for the cell that is currently being dragged @@ -149,6 +153,8 @@ public class Workspace extends SmoothPagedView private float[] mTempDragBottomRightCoordinates = new float[2]; private Matrix mTempInverseMatrix = new Matrix(); + private SpringLoadedDragController mSpringLoadedDragControllger; + private static final int DEFAULT_CELL_COUNT_X = 4; private static final int DEFAULT_CELL_COUNT_Y = 4; @@ -159,9 +165,10 @@ public class Workspace extends SmoothPagedView // in all apps or customize mode) private boolean mIsSmall = false; private boolean mIsInUnshrinkAnimation = false; - private AnimatorListener mUnshrinkAnimationListener; + private AnimatorListener mShrinkAnimationListener, mUnshrinkAnimationListener; enum ShrinkState { TOP, SPRING_LOADED, MIDDLE, BOTTOM_HIDDEN, BOTTOM_VISIBLE }; private ShrinkState mShrinkState; + private boolean mWasSpringLoadedOnDragExit = false; private boolean mWaitingToShrink = false; private ShrinkState mWaitingToShrinkState; private AnimatorSet mAnimator; @@ -677,7 +684,7 @@ public class Workspace extends SmoothPagedView for (int i = 0; i < getChildCount(); i++) { CellLayout cl = (CellLayout) getChildAt(i); if (cl != null) { - int totalDistance = cl.getMeasuredWidth() + mPageSpacing; + int totalDistance = getScaledMeasuredWidth(cl) + mPageSpacing; int delta = screenCenter - (getChildOffset(i) - getRelativeChildOffset(i) + halfScreenSize); @@ -938,7 +945,9 @@ public class Workspace extends SmoothPagedView // Stop any scrolling, move to the current page right away setCurrentPage((mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage); - updateWhichPagesAcceptDrops(mShrinkState); + if (!mIsDragInProcess) { + updateWhichPagesAcceptDrops(mShrinkState); + } // we intercept and reject all touch events when we're small, so be sure to reset the state mTouchState = TOUCH_STATE_REST; @@ -1039,6 +1048,7 @@ public class Workspace extends SmoothPagedView // increment newX for the next screen newX += scaledPageWidth + extraScaledSpacing; } + setLayoutScale(1.0f); if (animated) { mAnimator.start(); } @@ -1159,18 +1169,24 @@ public class Workspace extends SmoothPagedView * appearance). * */ - public void onDragStartedWithItemSpans(int spanX, int spanY) { - updateWhichPagesAcceptDropsDuringDrag(mShrinkState, spanX, spanY); - } + public void onDragStartedWithItemSpans(int spanX, int spanY, Bitmap b) { + mIsDragInProcess = true; + + final Canvas canvas = new Canvas(); - public void onDragStartedWithItemMinSize(int minWidth, int minHeight) { - int[] spanXY = CellLayout.rectToCell(getResources(), minWidth, minHeight, null); - onDragStartedWithItemSpans(spanXY[0], spanXY[1]); + // We need to add extra padding to the bitmap to make room for the glow effect + final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS; + + // The outline is used to visualize where the item will land if dropped + mDragOutline = createDragOutline(b, canvas, bitmapPadding); + + updateWhichPagesAcceptDropsDuringDrag(mShrinkState, spanX, spanY); } // we call this method whenever a drag and drop in Launcher finishes, even if Workspace was // never dragged over public void onDragStopped() { + mIsDragInProcess = false; updateWhichPagesAcceptDrops(mShrinkState); } @@ -1181,16 +1197,48 @@ public class Workspace extends SmoothPagedView // We call this when we trigger an unshrink by clicking on the CellLayout cl public void unshrink(CellLayout clThatWasClicked) { + unshrink(clThatWasClicked, false); + } + + public void unshrink(CellLayout clThatWasClicked, boolean springLoaded) { int newCurrentPage = indexOfChild(clThatWasClicked); if (mIsSmall) { + if (springLoaded) { + setLayoutScale(SPRING_LOADED_DRAG_SHRINK_FACTOR); + } moveToNewPageWithoutMovingCellLayouts(newCurrentPage); - unshrink(true); + unshrink(true, springLoaded); + } + } + + + public void enterSpringLoadedDragMode(CellLayout clThatWasClicked) { + mShrinkState = ShrinkState.SPRING_LOADED; + unshrink(clThatWasClicked, true); + mDragTargetLayout.onDragEnter(); + } + + public void exitSpringLoadedDragMode(ShrinkState shrinkState) { + shrink(shrinkState); + if (mDragTargetLayout != null) { + mDragTargetLayout.onDragExit(); } } void unshrink(boolean animated) { + unshrink(animated, false); + } + + void unshrink(boolean animated, boolean springLoaded) { if (mIsSmall) { - mIsSmall = false; + float finalScaleFactor = 1.0f; + float finalBackgroundAlpha = 0.0f; + if (springLoaded) { + finalScaleFactor = SPRING_LOADED_DRAG_SHRINK_FACTOR; + finalBackgroundAlpha = 1.0f; + } else { + mIsSmall = false; + } if (mAnimator != null) { mAnimator.cancel(); } @@ -1216,9 +1264,9 @@ public class Workspace extends SmoothPagedView ObjectAnimator animWithInterpolator = ObjectAnimator.ofPropertyValuesHolder(cl, PropertyValuesHolder.ofFloat("translationX", translation), PropertyValuesHolder.ofFloat("translationY", 0.0f), - PropertyValuesHolder.ofFloat("scaleX", 1.0f), - PropertyValuesHolder.ofFloat("scaleY", 1.0f), - PropertyValuesHolder.ofFloat("backgroundAlpha", 0.0f), + PropertyValuesHolder.ofFloat("scaleX", finalScaleFactor), + PropertyValuesHolder.ofFloat("scaleY", finalScaleFactor), + PropertyValuesHolder.ofFloat("backgroundAlpha", finalBackgroundAlpha), PropertyValuesHolder.ofFloat("alpha", finalAlphaValue), PropertyValuesHolder.ofFloat("rotationY", rotation)); animWithInterpolator.setDuration(duration); @@ -1227,8 +1275,8 @@ public class Workspace extends SmoothPagedView } else { cl.setTranslationX(translation); cl.setTranslationY(0.0f); - cl.setScaleX(1.0f); - cl.setScaleY(1.0f); + cl.setScaleX(finalScaleFactor); + cl.setScaleY(finalScaleFactor); cl.setBackgroundAlpha(0.0f); cl.setAlpha(finalAlphaValue); cl.setRotationY(rotation); @@ -1294,6 +1342,22 @@ public class Workspace extends SmoothPagedView return b; } + /** + * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. + * Responsibility for the bitmap is transferred to the caller. + */ + private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding) { + final int outlineColor = getResources().getColor(R.color.drag_outline_color); + final Bitmap b = Bitmap.createBitmap( + orig.getWidth() + padding, orig.getHeight() + padding, Bitmap.Config.ARGB_8888); + + canvas.setBitmap(b); + canvas.drawBitmap(orig, 0, 0, new Paint()); + mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor); + + return b; + } + /** * Creates a drag outline to represent a drop (that we don't have the actual information for * yet). May be changed in the future to alter the drop outline slightly depending on the @@ -1515,12 +1579,12 @@ public class Workspace extends SmoothPagedView } if (source != this) { - if (mIsSmall) { + if (!mIsSmall || mWasSpringLoadedOnDragExit) { + onDropExternal(originX, originY, dragInfo, mDragTargetLayout, false); + } else { // if we drag and drop to small screens, don't pass the touch x/y coords (when we // enable spring-loaded adding, however, we do want to pass the touch x/y coords) onDropExternal(-1, -1, dragInfo, mDragTargetLayout, false); - } else { - onDropExternal(originX, originY, dragInfo, mDragTargetLayout, false); } } else if (mDragInfo != null) { final View cell = mDragInfo.cell; @@ -1577,7 +1641,8 @@ public class Workspace extends SmoothPagedView // Prepare it to be animated into its new position // This must be called after the view has been re-parented setPositionForDropAnimation(dragView, originX, originY, parent, cell); - parent.onDropChild(cell); + boolean animateDrop = !mWasSpringLoadedOnDragExit; + parent.onDropChild(cell, animateDrop); } } @@ -1919,21 +1984,34 @@ public class Workspace extends SmoothPagedView CellLayout layout; int originX = x - xOffset; int originY = y - yOffset; - if (mIsSmall || mIsInUnshrinkAnimation) { + boolean shrunken = mIsSmall || mIsInUnshrinkAnimation; + if (shrunken) { layout = findMatchingPageForDragOver( dragView, originX, originY, xOffset, yOffset); if (layout != mDragTargetLayout) { if (mDragTargetLayout != null) { mDragTargetLayout.setHover(false); + mSpringLoadedDragControllger.onDragExit(); } mDragTargetLayout = layout; if (mDragTargetLayout != null && mDragTargetLayout.getAcceptsDrops()) { mDragTargetLayout.setHover(true); + mSpringLoadedDragControllger.onDragEnter(mDragTargetLayout); } } } else { layout = getCurrentDropLayout(); + if (layout != mDragTargetLayout) { + if (mDragTargetLayout != null) { + mDragTargetLayout.onDragExit(); + } + layout.onDragEnter(); + mDragTargetLayout = layout; + } + } + if (!shrunken || mShrinkState == ShrinkState.SPRING_LOADED) { + layout = getCurrentDropLayout(); final ItemInfo item = (ItemInfo)dragInfo; if (dragInfo instanceof LauncherAppWidgetInfo) { @@ -1964,23 +2042,12 @@ public class Workspace extends SmoothPagedView } } - if (layout != mDragTargetLayout) { - if (mDragTargetLayout != null) { - mDragTargetLayout.onDragExit(); - } - layout.onDragEnter(); - mDragTargetLayout = layout; - } - - // only visualize the drop locations for moving icons within the home screen on - // tablet on phone, we also visualize icons dragged in from All Apps - if ((!LauncherApplication.isScreenXLarge() || source == this) - && mDragTargetLayout != null) { + if (mDragTargetLayout != null) { final View child = (mDragInfo == null) ? null : mDragInfo.cell; - int localOriginX = originX - (mDragTargetLayout.getLeft() - mScrollX); - int localOriginY = originY - (mDragTargetLayout.getTop() - mScrollY); + float[] localOrigin = { originX, originY }; + mapPointFromSelfToChild(mDragTargetLayout, localOrigin, null); mDragTargetLayout.visualizeDropLocation(child, mDragOutline, - localOriginX, localOriginY, item.spanX, item.spanY); + (int) localOrigin[0], (int) localOrigin[1], item.spanX, item.spanY); } } } @@ -1988,12 +2055,16 @@ public class Workspace extends SmoothPagedView public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo) { + mWasSpringLoadedOnDragExit = mShrinkState == ShrinkState.SPRING_LOADED; if (mDragTargetLayout != null) { mDragTargetLayout.onDragExit(); } if (!mIsPageMoving) { hideOutlines(); } + if (mShrinkState == ShrinkState.SPRING_LOADED) { + mLauncher.exitSpringLoadedDragMode(); + } clearAllHovers(); } @@ -2083,7 +2154,9 @@ public class Workspace extends SmoothPagedView } addInScreen(view, indexOfChild(cellLayout), mTargetCell[0], mTargetCell[1], info.spanX, info.spanY, insertAtFirst); - cellLayout.onDropChild(view); + boolean animateDrop = !mWasSpringLoadedOnDragExit; + cellLayout.onDropChild(view, animateDrop); + cellLayout.animateDrop(); CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); LauncherModel.addOrMoveItemInDatabase(mLauncher, info, @@ -2136,6 +2209,7 @@ public class Workspace extends SmoothPagedView void setLauncher(Launcher launcher) { mLauncher = launcher; + mSpringLoadedDragControllger = new SpringLoadedDragController(mLauncher); } public void setDragController(DragController dragController) { @@ -2153,7 +2227,8 @@ public class Workspace extends SmoothPagedView // final Object tag = mDragInfo.cell.getTag(); } } else if (mDragInfo != null) { - ((CellLayout) getChildAt(mDragInfo.screen)).onDropChild(mDragInfo.cell); + boolean animateDrop = !mWasSpringLoadedOnDragExit; + ((CellLayout) getChildAt(mDragInfo.screen)).onDropChild(mDragInfo.cell, animateDrop); } mDragOutline = null; @@ -2214,6 +2289,7 @@ public class Workspace extends SmoothPagedView for (int i = 0; i < childCount; i++) { ((CellLayout) getChildAt(i)).setHover(false); } + mSpringLoadedDragControllger.onDragExit(); } @Override -- cgit v1.2.3