summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/launcher2/AppWidgetResizeFrame.java2
-rw-r--r--src/com/android/launcher2/AppsCustomizePagedView.java462
-rw-r--r--src/com/android/launcher2/AppsCustomizeTabHost.java126
-rw-r--r--src/com/android/launcher2/BubbleTextView.java35
-rw-r--r--src/com/android/launcher2/ButtonDropTarget.java33
-rw-r--r--src/com/android/launcher2/CellLayout.java1466
-rw-r--r--src/com/android/launcher2/CheckLongPressHelper.java62
-rw-r--r--src/com/android/launcher2/DeleteDropTarget.java242
-rw-r--r--src/com/android/launcher2/DragController.java236
-rw-r--r--src/com/android/launcher2/DragLayer.java238
-rw-r--r--src/com/android/launcher2/DragSource.java18
-rw-r--r--src/com/android/launcher2/DragView.java142
-rw-r--r--src/com/android/launcher2/DrawableStateProxyView.java66
-rw-r--r--src/com/android/launcher2/DropTarget.java11
-rw-r--r--src/com/android/launcher2/FastBitmapDrawable.java2
-rw-r--r--src/com/android/launcher2/FocusHelper.java11
-rw-r--r--src/com/android/launcher2/Folder.java140
-rw-r--r--src/com/android/launcher2/FolderIcon.java50
-rw-r--r--src/com/android/launcher2/HandleView.java3
-rw-r--r--src/com/android/launcher2/HolographicPagedViewIcon.java55
-rw-r--r--src/com/android/launcher2/Hotseat.java17
-rw-r--r--src/com/android/launcher2/IconCache.java23
-rw-r--r--src/com/android/launcher2/InfoDropTarget.java8
-rw-r--r--src/com/android/launcher2/InstallShortcutReceiver.java109
-rw-r--r--src/com/android/launcher2/InstallWidgetReceiver.java2
-rw-r--r--src/com/android/launcher2/ItemInfo.java9
-rw-r--r--src/com/android/launcher2/Launcher.java679
-rw-r--r--src/com/android/launcher2/LauncherAppWidgetHostView.java48
-rw-r--r--src/com/android/launcher2/LauncherApplication.java23
-rw-r--r--src/com/android/launcher2/LauncherModel.java48
-rw-r--r--src/com/android/launcher2/LauncherProvider.java6
-rw-r--r--src/com/android/launcher2/LauncherViewPropertyAnimator.java255
-rw-r--r--src/com/android/launcher2/PagedView.java202
-rw-r--r--src/com/android/launcher2/PagedViewGridLayout.java1
-rw-r--r--src/com/android/launcher2/PagedViewIcon.java167
-rw-r--r--src/com/android/launcher2/PagedViewWidget.java187
-rw-r--r--src/com/android/launcher2/PagedViewWithDraggableItems.java8
-rw-r--r--src/com/android/launcher2/PendingAddItemInfo.java26
-rw-r--r--src/com/android/launcher2/RocketLauncher.java4
-rw-r--r--src/com/android/launcher2/SearchDropTargetBar.java20
-rw-r--r--src/com/android/launcher2/ShortcutAndWidgetContainer.java (renamed from src/com/android/launcher2/CellLayoutChildren.java)24
-rw-r--r--src/com/android/launcher2/ShortcutInfo.java8
-rw-r--r--src/com/android/launcher2/SmoothPagedView.java10
-rw-r--r--src/com/android/launcher2/SpringLoadedDragController.java2
-rw-r--r--src/com/android/launcher2/Utilities.java1
-rw-r--r--src/com/android/launcher2/WallpaperChooserDialogFragment.java41
-rw-r--r--src/com/android/launcher2/Workspace.java1359
47 files changed, 4601 insertions, 2086 deletions
diff --git a/src/com/android/launcher2/AppWidgetResizeFrame.java b/src/com/android/launcher2/AppWidgetResizeFrame.java
index 6d132ebf0..c01a882cb 100644
--- a/src/com/android/launcher2/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher2/AppWidgetResizeFrame.java
@@ -80,7 +80,7 @@ public class AppWidgetResizeFrame extends FrameLayout {
mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace);
final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo();
- int[] result = mLauncher.getMinResizeSpanForWidget(info, null);
+ int[] result = mLauncher.getMinSpanForWidget(info, null);
mMinHSpan = result[0];
mMinVSpan = result[1];
diff --git a/src/com/android/launcher2/AppsCustomizePagedView.java b/src/com/android/launcher2/AppsCustomizePagedView.java
index 7f0edde75..840ec459d 100644
--- a/src/com/android/launcher2/AppsCustomizePagedView.java
+++ b/src/com/android/launcher2/AppsCustomizePagedView.java
@@ -19,6 +19,7 @@ package com.android.launcher2;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
+import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
@@ -33,9 +34,10 @@ import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.MaskFilter;
+import android.graphics.Matrix;
import android.graphics.Paint;
-import android.graphics.PorterDuff;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.TableMaskFilter;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
@@ -75,8 +77,7 @@ interface AsyncTaskCallback {
*/
class AsyncTaskPageData {
enum Type {
- LoadWidgetPreviewData,
- LoadHolographicIconsData
+ LoadWidgetPreviewData
}
AsyncTaskPageData(int p, ArrayList<Object> l, ArrayList<Bitmap> si, AsyncTaskCallback bgR,
@@ -167,7 +168,9 @@ class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTas
* The Apps/Customize page that displays all the applications, widgets, and shortcuts.
*/
public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
- AllAppsView, View.OnClickListener, View.OnKeyListener, DragSource {
+ AllAppsView, View.OnClickListener, View.OnKeyListener, DragSource,
+ PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener,
+ LauncherTransitionable {
static final String LOG_TAG = "AppsCustomizePagedView";
/**
@@ -186,6 +189,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
// Save and Restore
private int mSaveInstanceStateItemIndex = -1;
+ private PagedViewIcon mPressedIcon;
// Content
private ArrayList<ApplicationInfo> mApps;
@@ -200,7 +204,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
private Canvas mCanvas;
private Drawable mDefaultWidgetBackground;
private IconCache mIconCache;
- private int mDragViewMultiplyColor;
// Dimens
private int mContentWidth;
@@ -226,9 +229,23 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
// Previews & outlines
ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
- private HolographicOutlineHelper mHolographicOutlineHelper;
private static final int sPageSleepDelay = 200;
+ private Runnable mInflateWidgetRunnable = null;
+ private Runnable mBindWidgetRunnable = null;
+ static final int WIDGET_NO_CLEANUP_REQUIRED = -1;
+ static final int WIDGET_BOUND = 0;
+ static final int WIDGET_INFLATED = 1;
+ int mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
+ int mWidgetLoadingId = -1;
+ PendingAddWidgetInfo mCreateWidgetInfo = null;
+ private boolean mDraggingWidget = false;
+
+ // Deferral of loading widget previews during launcher transitions
+ private boolean mInTransition;
+ private ArrayList<AsyncTaskPageData> mDeferredSyncWidgetPageItems =
+ new ArrayList<AsyncTaskPageData>();
+
public AppsCustomizePagedView(Context context, AttributeSet attrs) {
super(context, attrs);
mLayoutInflater = LayoutInflater.from(context);
@@ -236,7 +253,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
mApps = new ArrayList<ApplicationInfo>();
mWidgets = new ArrayList<Object>();
mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache();
- mHolographicOutlineHelper = new HolographicOutlineHelper();
mCanvas = new Canvas();
mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>();
@@ -244,7 +260,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
Resources resources = context.getResources();
mDefaultWidgetBackground = resources.getDrawable(R.drawable.default_widget_preview_holo);
mAppIconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size);
- mDragViewMultiplyColor = resources.getColor(R.color.drag_view_multiply_color);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
mMaxAppCellCountX = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountX, -1);
@@ -495,12 +510,12 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
if (v instanceof PagedViewIcon) {
// Animate some feedback to the click
final ApplicationInfo appInfo = (ApplicationInfo) v.getTag();
- animateClickFeedback(v, new Runnable() {
- @Override
- public void run() {
- mLauncher.startActivitySafely(appInfo.intent, appInfo);
- }
- });
+ mLauncher.startActivitySafely(appInfo.intent, appInfo);
+
+ // Lock the drawable state to pressed until we return to Launcher
+ if (mPressedIcon != null) {
+ mPressedIcon.lockDrawableState();
+ }
} else if (v instanceof PagedViewWidget) {
// Let the user know that they have to long press to add a widget
Toast.makeText(getContext(), R.string.long_press_widget_to_add,
@@ -537,7 +552,72 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
mLauncher.getWorkspace().beginDragShared(v, this);
}
+ private void loadWidgetInBackground(final PendingAddWidgetInfo info) {
+ final AppWidgetProviderInfo pInfo = info.info;
+ if (pInfo.configure != null) {
+ return;
+ }
+
+ mBindWidgetRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId();
+ AppWidgetManager.getInstance(mLauncher).bindAppWidgetId(mWidgetLoadingId,
+ info.componentName);
+ mWidgetCleanupState = WIDGET_BOUND;
+ }
+ };
+ post(mBindWidgetRunnable);
+
+ mInflateWidgetRunnable = new Runnable() {
+ @Override
+ public void run() {
+ AppWidgetHostView hostView =
+ mLauncher.getAppWidgetHost().createView(mContext, mWidgetLoadingId, pInfo);
+ info.boundWidget = hostView;
+ mWidgetCleanupState = WIDGET_INFLATED;
+ hostView.setVisibility(INVISIBLE);
+ mLauncher.getDragLayer().addView(hostView);
+ }
+ };
+ post(mInflateWidgetRunnable);
+ }
+
+ @Override
+ public void onShortPress(View v) {
+ // We are anticipating a long press, and we use this time to load bind and instantiate
+ // the widget. This will need to be cleaned up if it turns out no long press occurs.
+ mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag());
+ loadWidgetInBackground(mCreateWidgetInfo);
+ }
+
+ private void cleanupWidgetPreloading() {
+ PendingAddWidgetInfo info = mCreateWidgetInfo;
+ mCreateWidgetInfo = null;
+ if (mWidgetCleanupState >= 0 && mWidgetLoadingId != -1) {
+ mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
+ }
+ if (mWidgetCleanupState == WIDGET_BOUND) {
+ removeCallbacks(mInflateWidgetRunnable);
+ } else if (mWidgetCleanupState == WIDGET_INFLATED) {
+ AppWidgetHostView widget = info.boundWidget;
+ int widgetId = widget.getAppWidgetId();
+ mLauncher.getAppWidgetHost().deleteAppWidgetId(widgetId);
+ mLauncher.getDragLayer().removeView(widget);
+ }
+ mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
+ mWidgetLoadingId = -1;
+ }
+
+ @Override
+ public void cleanUpShortPress(View v) {
+ if (!mDraggingWidget) {
+ cleanupWidgetPreloading();
+ }
+ }
+
private void beginDraggingWidget(View v) {
+ mDraggingWidget = true;
// Get the widget preview as the drag representation
ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
@@ -545,16 +625,37 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
// Compose the drag image
Bitmap preview;
Bitmap outline;
+ float scale = 1f;
if (createItemInfo instanceof PendingAddWidgetInfo) {
- PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo;
+ PendingAddWidgetInfo createWidgetInfo = mCreateWidgetInfo;
+ createItemInfo = createWidgetInfo;
int[] spanXY = mLauncher.getSpanForWidget(createWidgetInfo, null);
+ int[] size = mLauncher.getWorkspace().estimateItemSize(spanXY[0],
+ spanXY[1], createWidgetInfo, true);
createItemInfo.spanX = spanXY[0];
createItemInfo.spanY = spanXY[1];
-
- int[] maxSize = mLauncher.getWorkspace().estimateItemSize(spanXY[0], spanXY[1],
- createWidgetInfo, true);
+ int[] minSpanXY = mLauncher.getMinSpanForWidget(createWidgetInfo, null);
+ createWidgetInfo.minSpanX = minSpanXY[0];
+ createWidgetInfo.minSpanY = minSpanXY[1];
+
+ FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable();
+ float minScale = 1.25f;
+ int minWidth, minHeight;
+ minWidth = Math.max((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]);
+ minHeight = Math.max((int) (previewDrawable.getIntrinsicHeight() * minScale), size[1]);
preview = getWidgetPreview(createWidgetInfo.componentName, createWidgetInfo.previewImage,
- createWidgetInfo.icon, spanXY[0], spanXY[1], maxSize[0], maxSize[1]);
+ createWidgetInfo.icon, spanXY[0], spanXY[1], minWidth, minHeight);
+
+ // Determine the image view drawable scale relative to the preview
+ float[] mv = new float[9];
+ Matrix m = new Matrix();
+ m.setRectToRect(
+ new RectF(0f, 0f, (float) preview.getWidth(), (float) preview.getHeight()),
+ new RectF(0f, 0f, (float) previewDrawable.getIntrinsicWidth(),
+ (float) previewDrawable.getIntrinsicHeight()),
+ Matrix.ScaleToFit.START);
+ m.getValues(mv);
+ scale = (float) mv[0];
} else {
// Workaround for the fact that we don't keep the original ResolveInfo associated with
// the shortcut around. To get the icon, we just render the preview image (which has
@@ -581,51 +682,98 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
// Save the preview for the outline generation, then dim the preview
outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(),
false);
- mCanvas.setBitmap(preview);
- mCanvas.drawColor(mDragViewMultiplyColor, PorterDuff.Mode.MULTIPLY);
- mCanvas.setBitmap(null);
// Start the drag
alphaClipPaint = null;
mLauncher.lockScreenOrientationOnLargeUI();
mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, alphaClipPaint);
mDragController.startDrag(image, preview, this, createItemInfo,
- DragController.DRAG_ACTION_COPY, null);
+ DragController.DRAG_ACTION_COPY, null, scale);
outline.recycle();
preview.recycle();
}
- @Override
- protected boolean beginDragging(View v) {
- // Dismiss the cling
- mLauncher.dismissAllAppsCling(null);
+ @Override
+ protected boolean beginDragging(final View v) {
if (!super.beginDragging(v)) return false;
- // Go into spring loaded mode (must happen before we startDrag())
- mLauncher.enterSpringLoadedDragMode();
-
if (v instanceof PagedViewIcon) {
beginDraggingApplication(v);
} else if (v instanceof PagedViewWidget) {
beginDraggingWidget(v);
}
+
+ // We delay entering spring-loaded mode slightly to make sure the UI
+ // thready is free of any work.
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ // We don't enter spring-loaded mode if the drag has been cancelled
+ if (mLauncher.getDragController().isDragging()) {
+ // Dismiss the cling
+ mLauncher.dismissAllAppsCling(null);
+
+ // Reset the alpha on the dragged icon before we drag
+ resetDrawableState();
+
+ // Go into spring loaded mode (must happen before we startDrag())
+ mLauncher.enterSpringLoadedDragMode();
+ }
+ }
+ }, 150);
+
return true;
}
- private void endDragging(View target, boolean success) {
+
+ /**
+ * Clean up after dragging.
+ *
+ * @param target where the item was dragged to (can be null if the item was flung)
+ */
+ private void endDragging(View target, boolean isFlingToDelete, boolean success) {
mLauncher.getWorkspace().onDragStopped(success);
- if (!success || (target != mLauncher.getWorkspace() &&
+ if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
!(target instanceof DeleteDropTarget))) {
// Exit spring loaded mode if we have not successfully dropped or have not handled the
// drop in Workspace
mLauncher.exitSpringLoadedDragMode();
}
mLauncher.unlockScreenOrientationOnLargeUI();
+ }
+
+ @Override
+ public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+ mInTransition = true;
+ if (toWorkspace) {
+ cancelAllTasks();
+ }
+ }
+
+ @Override
+ public View getContent() {
+ return null;
+ }
+
+ @Override
+ public void onLauncherTransitionStep(Launcher l, float t) {
+ }
+ @Override
+ public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+ mInTransition = false;
+ for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) {
+ onSyncWidgetPageItems(d);
+ }
+ mDeferredSyncWidgetPageItems.clear();
}
@Override
- public void onDropCompleted(View target, DragObject d, boolean success) {
- endDragging(target, success);
+ public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
+ boolean success) {
+ // Return early and wait for onFlingToDeleteCompleted if this was the result of a fling
+ if (isFlingToDelete) return;
+
+ endDragging(target, false, success);
// Display an error message if the drag failed due to there not being enough space on the
// target layout we were dropping on.
@@ -643,9 +791,26 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
}
}
if (showOutOfSpaceMessage) {
- mLauncher.showOutOfSpaceMessage();
+ mLauncher.showOutOfSpaceMessage(false);
}
+
+ d.deferDragViewCleanupPostAnimation = false;
+ cleanupWidgetPreloading();
}
+ mDraggingWidget = false;
+ }
+
+ @Override
+ public void onFlingToDeleteCompleted() {
+ // We just dismiss the drag when we fling, so cleanup here
+ endDragging(null, true, true);
+ cleanupWidgetPreloading();
+ mDraggingWidget = false;
+ }
+
+ @Override
+ public boolean supportsFlingToDelete() {
+ return true;
}
@Override
@@ -673,6 +838,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
task.cancel(false);
iter.remove();
+ mDirtyPageContent.set(task.page, true);
}
}
@@ -692,7 +858,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
while (iter.hasNext()) {
AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
- int pageIndex = task.page + mNumAppsPages;
+ int pageIndex = task.page;
if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) ||
(mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) {
task.setThreadPriority(getThreadPriorityForPage(pageIndex));
@@ -759,7 +925,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
ApplicationInfo info = mApps.get(i);
PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
R.layout.apps_customize_application, layout, false);
- icon.applyFromApplicationInfo(info, true, mHolographicOutlineHelper);
+ icon.applyFromApplicationInfo(info, true, this);
icon.setOnClickListener(this);
icon.setOnLongClickListener(this);
icon.setOnTouchListener(this);
@@ -775,12 +941,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
}
layout.createHardwareLayers();
-
- /* TEMPORARILY DISABLE HOLOGRAPHIC ICONS
- if (mFadeInAdjacentScreens) {
- prepareGenerateHoloOutlinesTask(page, items, images);
- }
- */
}
/**
@@ -800,7 +960,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
int minPageDiff = Integer.MAX_VALUE;
while (iter.hasNext()) {
AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
- minPageDiff = Math.abs(task.page + mNumAppsPages - toPage);
+ minPageDiff = Math.abs(task.page - toPage);
}
int rawPageDiff = Math.abs(page - toPage);
@@ -835,7 +995,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
while (iter.hasNext()) {
AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
- int taskPage = task.page + mNumAppsPages;
+ int taskPage = task.page;
if (taskPage < getAssociatedLowerPageBound(mCurrentPage) ||
taskPage > getAssociatedUpperPageBound(mCurrentPage)) {
task.cancel(false);
@@ -846,7 +1006,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
}
// We introduce a slight delay to order the loading of side pages so that we don't thrash
- final int sleepMs = getSleepForPage(page + mNumAppsPages);
+ final int sleepMs = getSleepForPage(page);
AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
new AsyncTaskCallback() {
@Override
@@ -866,96 +1026,20 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
new AsyncTaskCallback() {
@Override
public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
- try {
- mRunningTasks.remove(task);
- if (task.isCancelled()) return;
- onSyncWidgetPageItems(data);
- } finally {
- data.cleanup(task.isCancelled());
- }
+ mRunningTasks.remove(task);
+ if (task.isCancelled()) return;
+ // do cleanup inside onSyncWidgetPageItems
+ onSyncWidgetPageItems(data);
}
});
// Ensure that the task is appropriately prioritized and runs in parallel
AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
AsyncTaskPageData.Type.LoadWidgetPreviewData);
- t.setThreadPriority(getThreadPriorityForPage(page + mNumAppsPages));
+ t.setThreadPriority(getThreadPriorityForPage(page));
t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData);
mRunningTasks.add(t);
}
- /**
- * Creates and executes a new AsyncTask to load the outlines for a page of content.
- */
- private void prepareGenerateHoloOutlinesTask(int page, ArrayList<Object> items,
- ArrayList<Bitmap> images) {
- // Prune old tasks for this page
- Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
- while (iter.hasNext()) {
- AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
- int taskPage = task.page;
- if ((taskPage == page) &&
- (task.dataType == AsyncTaskPageData.Type.LoadHolographicIconsData)) {
- task.cancel(false);
- iter.remove();
- }
- }
-
- AsyncTaskPageData pageData = new AsyncTaskPageData(page, items, images,
- new AsyncTaskCallback() {
- @Override
- public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
- try {
- // Ensure that this task starts running at the correct priority
- task.syncThreadPriority();
-
- ArrayList<Bitmap> images = data.generatedImages;
- ArrayList<Bitmap> srcImages = data.sourceImages;
- int count = srcImages.size();
- Canvas c = new Canvas();
- for (int i = 0; i < count && !task.isCancelled(); ++i) {
- // Before work on each item, ensure that this task is running at the correct
- // priority
- task.syncThreadPriority();
-
- Bitmap b = srcImages.get(i);
- Bitmap outline = Bitmap.createBitmap(b.getWidth(), b.getHeight(),
- Bitmap.Config.ARGB_8888);
-
- c.setBitmap(outline);
- c.save();
- c.drawBitmap(b, 0, 0, null);
- c.restore();
- c.setBitmap(null);
-
- images.add(outline);
- }
- } finally {
- if (task.isCancelled()) {
- data.cleanup(true);
- }
- }
- }
- },
- new AsyncTaskCallback() {
- @Override
- public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
- try {
- mRunningTasks.remove(task);
- if (task.isCancelled()) return;
- onHolographicPageItemsLoaded(data);
- } finally {
- data.cleanup(task.isCancelled());
- }
- }
- });
-
- // Ensure that the outline task always runs in the background, serially
- AppsCustomizeAsyncTask t =
- new AppsCustomizeAsyncTask(page, AsyncTaskPageData.Type.LoadHolographicIconsData);
- t.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- t.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, pageData);
- mRunningTasks.add(t);
- }
/*
* Widgets PagedView implementation
@@ -985,9 +1069,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
d.setBounds(x, y, x + w, y + h);
d.draw(c);
d.setBounds(oldBounds); // Restore the bounds
- if (multiplyColor != 0xFFFFFFFF) {
- c.drawColor(mDragViewMultiplyColor, PorterDuff.Mode.MULTIPLY);
- }
c.setBitmap(null);
}
}
@@ -1025,11 +1106,9 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
if (widgetPreviewExists) {
bitmapWidth = drawable.getIntrinsicWidth();
bitmapHeight = drawable.getIntrinsicHeight();
-
- // Cap the size so widget previews don't appear larger than the actual widget
- maxWidth = Math.min(maxWidth, mWidgetSpacingLayout.estimateCellWidth(cellHSpan));
- maxHeight = Math.min(maxHeight, mWidgetSpacingLayout.estimateCellHeight(cellVSpan));
} else {
+ if (cellHSpan < 1) cellHSpan = 1;
+ if (cellVSpan < 1) cellVSpan = 1;
// Determine the size of the bitmap for the preview image we will generate
// TODO: This actually uses the apps customize cell layout params, where as we make want
// the Workspace params for more accuracy.
@@ -1102,13 +1181,13 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
- ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY);
// Prepare the set of widgets to load previews for in the background
- int offset = page * numItemsPerPage;
+ int offset = (page - mNumAppsPages) * numItemsPerPage;
for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) {
items.add(mWidgets.get(i));
}
// Prepopulate the pages with the other widget info, and fill in the previews later
- final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page + mNumAppsPages);
+ final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
layout.setColumnCount(layout.getCellCountX());
for (int i = 0; i < items.size(); ++i) {
Object rawInfo = items.get(i);
@@ -1120,9 +1199,9 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
createItemInfo = new PendingAddWidgetInfo(info, null, null);
int[] cellSpans = mLauncher.getSpanForWidget(info, null);
- widget.applyFromAppWidgetProviderInfo(info, -1, cellSpans,
- mHolographicOutlineHelper);
+ widget.applyFromAppWidgetProviderInfo(info, -1, cellSpans);
widget.setTag(createItemInfo);
+ widget.setShortPressListener(this);
} else if (rawInfo instanceof ResolveInfo) {
// Fill in the shortcuts information
ResolveInfo info = (ResolveInfo) rawInfo;
@@ -1130,7 +1209,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
info.activityInfo.name);
- widget.applyFromResolveInfo(mPackageManager, info, mHolographicOutlineHelper);
+ widget.applyFromResolveInfo(mPackageManager, info);
widget.setTag(createItemInfo);
}
widget.setOnClickListener(this);
@@ -1204,8 +1283,13 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
if (rawInfo instanceof AppWidgetProviderInfo) {
AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
int[] cellSpans = mLauncher.getSpanForWidget(info, null);
+
+ int maxWidth = Math.min(data.maxImageWidth,
+ mWidgetSpacingLayout.estimateCellWidth(cellSpans[0]));
+ int maxHeight = Math.min(data.maxImageHeight,
+ mWidgetSpacingLayout.estimateCellHeight(cellSpans[1]));
Bitmap b = getWidgetPreview(info.provider, info.previewImage, info.icon,
- cellSpans[0], cellSpans[1], data.maxImageWidth, data.maxImageHeight);
+ cellSpans[0], cellSpans[1], maxWidth, maxHeight);
images.add(b);
} else if (rawInfo instanceof ResolveInfo) {
// Fill in the shortcuts information
@@ -1214,58 +1298,38 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
}
}
}
- private void onSyncWidgetPageItems(AsyncTaskPageData data) {
- int page = data.page;
- PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page + mNumAppsPages);
- ArrayList<Object> items = data.items;
- int count = items.size();
- for (int i = 0; i < count; ++i) {
- PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
- if (widget != null) {
- Bitmap preview = data.generatedImages.get(i);
- widget.applyPreview(new FastBitmapDrawable(preview), i);
- }
- }
-
- layout.createHardwareLayer();
- invalidate();
-
- /* TEMPORARILY DISABLE HOLOGRAPHIC ICONS
- if (mFadeInAdjacentScreens) {
- prepareGenerateHoloOutlinesTask(data.page, data.items, data.generatedImages);
- }
- */
-
- // Update all thread priorities
- Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
- while (iter.hasNext()) {
- AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
- int pageIndex = task.page + mNumAppsPages;
- task.setThreadPriority(getThreadPriorityForPage(pageIndex));
+ private void onSyncWidgetPageItems(AsyncTaskPageData data) {
+ if (mInTransition) {
+ mDeferredSyncWidgetPageItems.add(data);
+ return;
}
- }
- private void onHolographicPageItemsLoaded(AsyncTaskPageData data) {
- // Invalidate early to short-circuit children invalidates
- invalidate();
+ try {
+ int page = data.page;
+ PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
- int page = data.page;
- ViewGroup layout = (ViewGroup) getPageAt(page);
- if (layout instanceof PagedViewCellLayout) {
- PagedViewCellLayout cl = (PagedViewCellLayout) layout;
- int count = cl.getPageChildCount();
- if (count != data.generatedImages.size()) return;
+ ArrayList<Object> items = data.items;
+ int count = items.size();
for (int i = 0; i < count; ++i) {
- PagedViewIcon icon = (PagedViewIcon) cl.getChildOnPageAt(i);
- icon.setHolographicOutline(data.generatedImages.get(i));
+ PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
+ if (widget != null) {
+ Bitmap preview = data.generatedImages.get(i);
+ widget.applyPreview(new FastBitmapDrawable(preview), i);
+ }
}
- } else {
- int count = layout.getChildCount();
- if (count != data.generatedImages.size()) return;
- for (int i = 0; i < count; ++i) {
- View v = layout.getChildAt(i);
- ((PagedViewWidget) v).setHolographicOutline(data.generatedImages.get(i));
+
+ layout.createHardwareLayer();
+ invalidate();
+
+ // Update all thread priorities
+ Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
+ while (iter.hasNext()) {
+ AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
+ int pageIndex = task.page;
+ task.setThreadPriority(getThreadPriorityForPage(pageIndex));
}
+ } finally {
+ data.cleanup(false);
}
}
@@ -1279,7 +1343,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
mWidgetCountY);
setupPage(layout);
- addView(layout, new PagedViewGridLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
}
@@ -1295,14 +1359,14 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
if (page < mNumAppsPages) {
syncAppsPageItems(page, immediate);
} else {
- syncWidgetPageItems(page - mNumAppsPages, immediate);
+ syncWidgetPageItems(page, immediate);
}
}
// We want our pages to be z-ordered such that the further a page is to the left, the higher
// it is in the z-order. This is important to insure touch events are handled correctly.
View getPageAt(int index) {
- return getChildAt(getChildCount() - index - 1);
+ return getChildAt(indexToPage(index));
}
@Override
@@ -1490,6 +1554,9 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
@Override
public void reset() {
+ // If we have reset, then we should not continue to restore the previous state
+ mSaveInstanceStateItemIndex = -1;
+
AppsCustomizeTabHost tabHost = getTabHost();
String tag = tabHost.getCurrentTabTag();
if (tag != null) {
@@ -1497,6 +1564,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
tabHost.setCurrentTabFromContent(ContentType.Applications);
}
}
+
if (mCurrentPage != 0) {
invalidatePageData(0);
}
@@ -1540,6 +1608,22 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
cancelAllTasks();
}
+ @Override
+ public void iconPressed(PagedViewIcon icon) {
+ // Reset the previously pressed icon and store a reference to the pressed icon so that
+ // we can reset it on return to Launcher (in Launcher.onResume())
+ if (mPressedIcon != null) {
+ mPressedIcon.resetDrawableState();
+ }
+ mPressedIcon = icon;
+ }
+
+ public void resetDrawableState() {
+ if (mPressedIcon != null) {
+ mPressedIcon.resetDrawableState();
+ mPressedIcon = null;
+ }
+ }
/*
* We load an extra page on each side to prevent flashes from scrolling and loading of the
diff --git a/src/com/android/launcher2/AppsCustomizeTabHost.java b/src/com/android/launcher2/AppsCustomizeTabHost.java
index 2963240fa..0199d01c6 100644
--- a/src/com/android/launcher2/AppsCustomizeTabHost.java
+++ b/src/com/android/launcher2/AppsCustomizeTabHost.java
@@ -53,12 +53,19 @@ public class AppsCustomizeTabHost extends TabHost implements LauncherTransitiona
private LinearLayout mContent;
private boolean mInTransition;
+ private boolean mTransitioningToWorkspace;
private boolean mResetAfterTransition;
- private Animator mLauncherTransition;
+ private Runnable mRelayoutAndMakeVisible;
public AppsCustomizeTabHost(Context context, AttributeSet attrs) {
super(context, attrs);
mLayoutInflater = LayoutInflater.from(context);
+ mRelayoutAndMakeVisible = new Runnable() {
+ public void run() {
+ mTabs.requestLayout();
+ mTabsContainer.setAlpha(1f);
+ }
+ };
}
/**
@@ -144,19 +151,29 @@ public class AppsCustomizeTabHost extends TabHost implements LauncherTransitiona
if (contentWidth > 0 && mTabs.getLayoutParams().width != contentWidth) {
// Set the width and show the tab bar
mTabs.getLayoutParams().width = contentWidth;
- post(new Runnable() {
- public void run() {
- mTabs.requestLayout();
- mTabsContainer.setAlpha(1f);
- }
- });
+ post(mRelayoutAndMakeVisible);
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // If we are mid transition then intercept touch events here so we can ignore them
+ if (mInTransition) {
+ return true;
+ }
+ return super.onInterceptTouchEvent(ev);
+ };
+
@Override
public boolean onTouchEvent(MotionEvent event) {
+ // Allow touch events to fall through if we are transitioning to the workspace
+ if (mInTransition) {
+ if (mTransitioningToWorkspace) {
+ return super.onTouchEvent(event);
+ }
+ }
+
// Intercept all touch events up to the bottom of the AppsCustomizePane so they do not fall
// through to the workspace and trigger showWorkspace()
if (event.getY() < mAppsCustomizePane.getBottom()) {
@@ -238,8 +255,8 @@ public class AppsCustomizeTabHost extends TabHost implements LauncherTransitiona
PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(true);
mAnimationBuffer.setAlpha(1f);
mAnimationBuffer.setVisibility(View.VISIBLE);
- LayoutParams p = new FrameLayout.LayoutParams(child.getWidth(),
- child.getHeight());
+ LayoutParams p = new FrameLayout.LayoutParams(child.getMeasuredWidth(),
+ child.getMeasuredHeight());
p.setMargins((int) child.getLeft(), (int) child.getTop(), 0, 0);
mAnimationBuffer.addView(child, p);
}
@@ -338,61 +355,72 @@ public class AppsCustomizeTabHost extends TabHost implements LauncherTransitiona
// force building the layer, so you don't get a blip early in an animation
// when the layer is created layer
buildLayer();
+
+ // Let the GC system know that now is a good time to do any garbage
+ // collection; makes it less likely we'll get a GC during the all apps
+ // to workspace animation
+ System.gc();
}
}
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (mLauncherTransition != null) {
- enableAndBuildHardwareLayer();
- mLauncherTransition.start();
- mLauncherTransition = null;
- }
+ @Override
+ public View getContent() {
+ return mContent;
}
/* LauncherTransitionable overrides */
@Override
- public boolean onLauncherTransitionStart(Launcher l, Animator animation, boolean toWorkspace) {
+ public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+ mAppsCustomizePane.onLauncherTransitionStart(l, animated, toWorkspace);
mInTransition = true;
- boolean delayLauncherTransitionUntilLayout = false;
- boolean animated = (animation != null);
- mLauncherTransition = null;
-
- // if the content wasn't visible before, delay the launcher animation until after a call
- // to layout -- this prevents a blip
- if (animated && mContent.getVisibility() == GONE) {
- mLauncherTransition = animation;
- delayLauncherTransitionUntilLayout = true;
- }
- mContent.setVisibility(VISIBLE);
+ mTransitioningToWorkspace = toWorkspace;
+
+ if (toWorkspace) {
+ // Going from All Apps -> Workspace
+ setVisibilityOfSiblingsWithLowerZOrder(VISIBLE);
+ // Stop the scrolling indicator - we don't want All Apps to be invalidating itself
+ // during the transition, especially since it has a hardware layer set on it
+ mAppsCustomizePane.cancelScrollingIndicatorAnimations();
+ } else {
+ // Going from Workspace -> All Apps
+ mContent.setVisibility(VISIBLE);
- if (!toWorkspace) {
// Make sure the current page is loaded (we start loading the side pages after the
// transition to prevent slowing down the animation)
mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true);
- }
- if (animated && !delayLauncherTransitionUntilLayout) {
- enableAndBuildHardwareLayer();
- }
- if (!toWorkspace && !LauncherApplication.isScreenLarge()) {
- mAppsCustomizePane.showScrollingIndicator(false);
+ if (!LauncherApplication.isScreenLarge()) {
+ mAppsCustomizePane.showScrollingIndicator(true);
+ }
}
+
if (mResetAfterTransition) {
mAppsCustomizePane.reset();
mResetAfterTransition = false;
}
- return delayLauncherTransitionUntilLayout;
+
+ if (animated) {
+ enableAndBuildHardwareLayer();
+ }
+ }
+
+ @Override
+ public void onLauncherTransitionStep(Launcher l, float t) {
+ // Do nothing
}
@Override
- public void onLauncherTransitionEnd(Launcher l, Animator animation, boolean toWorkspace) {
+ public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+ mAppsCustomizePane.onLauncherTransitionEnd(l, animated, toWorkspace);
mInTransition = false;
- if (animation != null) {
+ if (animated) {
setLayerType(LAYER_TYPE_NONE, null);
}
if (!toWorkspace) {
+ // Going from Workspace -> All Apps
+ setVisibilityOfSiblingsWithLowerZOrder(INVISIBLE);
+
// Dismiss the workspace cling and show the all apps cling (if not already shown)
l.dismissWorkspaceCling(null);
mAppsCustomizePane.showAllAppsCling();
@@ -406,7 +434,27 @@ public class AppsCustomizeTabHost extends TabHost implements LauncherTransitiona
}
}
- public void onResume() {
+ private void setVisibilityOfSiblingsWithLowerZOrder(int visibility) {
+ ViewGroup parent = (ViewGroup) getParent();
+ final int count = parent.getChildCount();
+ if (!isChildrenDrawingOrderEnabled()) {
+ for (int i = 0; i < count; i++) {
+ final View child = parent.getChildAt(i);
+ if (child == this) {
+ break;
+ } else {
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+ child.setVisibility(visibility);
+ }
+ }
+ } else {
+ throw new RuntimeException("Failed; can't get z-order of views");
+ }
+ }
+
+ public void onWindowVisible() {
if (getVisibility() == VISIBLE) {
mContent.setVisibility(VISIBLE);
// We unload the widget previews when the UI is hidden, so need to reload pages
diff --git a/src/com/android/launcher2/BubbleTextView.java b/src/com/android/launcher2/BubbleTextView.java
index dc498a403..a775ed5ab 100644
--- a/src/com/android/launcher2/BubbleTextView.java
+++ b/src/com/android/launcher2/BubbleTextView.java
@@ -47,8 +47,6 @@ public class BubbleTextView extends TextView {
static final float PADDING_H = 8.0f;
static final float PADDING_V = 3.0f;
- private Paint mPaint;
- private float mBubbleColorAlpha;
private int mPrevAlpha = -1;
private final HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper();
@@ -65,6 +63,7 @@ public class BubbleTextView extends TextView {
private Drawable mBackground;
private boolean mStayPressed;
+ private CheckLongPressHelper mLongPressHelper;
public BubbleTextView(Context context) {
super(context);
@@ -82,13 +81,11 @@ public class BubbleTextView extends TextView {
}
private void init() {
+ mLongPressHelper = new CheckLongPressHelper(this);
mBackground = getBackground();
final Resources res = getContext().getResources();
int bubbleColor = res.getColor(R.color.bubble_dark_background);
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPaint.setColor(bubbleColor);
- mBubbleColorAlpha = Color.alpha(bubbleColor) / 255.0f;
mFocusedOutlineColor = mFocusedGlowColor = mPressedOutlineColor = mPressedGlowColor =
res.getColor(android.R.color.holo_blue_light);
@@ -134,7 +131,7 @@ public class BubbleTextView extends TextView {
mPressedOrFocusedBackground = null;
}
if (isFocused()) {
- if (mLayout == null) {
+ if (getLayout() == null) {
// In some cases, we get focus before we have been layed out. Set the
// background to null so that it will get created when the view is drawn.
mPressedOrFocusedBackground = null;
@@ -176,6 +173,8 @@ public class BubbleTextView extends TextView {
// The translate of scrollX and scrollY is necessary when drawing TextViews, because
// they set scrollX and scrollY to large values to achieve centered text
destCanvas.save();
+ destCanvas.scale(getScaleX(), getScaleY(),
+ (getWidth() + padding) / 2, (getHeight() + padding) / 2);
destCanvas.translate(-getScrollX() + padding / 2, -getScrollY() + padding / 2);
destCanvas.clipRect(clipRect, Op.REPLACE);
draw(destCanvas);
@@ -222,6 +221,8 @@ public class BubbleTextView extends TextView {
} else {
mDidInvalidateForPressedState = false;
}
+
+ mLongPressHelper.postCheckForLongPress();
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
@@ -230,6 +231,8 @@ public class BubbleTextView extends TextView {
if (!isPressed()) {
mPressedOrFocusedBackground = null;
}
+
+ mLongPressHelper.cancelLongPress();
break;
}
return result;
@@ -244,8 +247,8 @@ public class BubbleTextView extends TextView {
}
void setCellLayoutPressedOrFocusedIcon() {
- if (getParent() instanceof CellLayoutChildren) {
- CellLayoutChildren parent = (CellLayoutChildren) getParent();
+ if (getParent() instanceof ShortcutAndWidgetContainer) {
+ ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) getParent();
if (parent != null) {
CellLayout layout = (CellLayout) parent.getParent();
layout.setPressedOrFocusedIcon((mPressedOrFocusedBackground != null) ? this : null);
@@ -286,6 +289,14 @@ public class BubbleTextView extends TextView {
canvas.translate(-scrollX, -scrollY);
}
}
+
+ // If text is transparent, don't draw any shadow
+ if (getCurrentTextColor() == getResources().getColor(android.R.color.transparent)) {
+ getPaint().clearShadowLayer();
+ super.draw(canvas);
+ return;
+ }
+
// We enhance the shadow by drawing the shadow twice
getPaint().setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR);
super.draw(canvas);
@@ -313,9 +324,15 @@ public class BubbleTextView extends TextView {
protected boolean onSetAlpha(int alpha) {
if (mPrevAlpha != alpha) {
mPrevAlpha = alpha;
- mPaint.setAlpha((int) (alpha * mBubbleColorAlpha));
super.onSetAlpha(alpha);
}
return true;
}
+
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+
+ mLongPressHelper.cancelLongPress();
+ }
}
diff --git a/src/com/android/launcher2/ButtonDropTarget.java b/src/com/android/launcher2/ButtonDropTarget.java
index 4ff7c9669..e9f8ce8b5 100644
--- a/src/com/android/launcher2/ButtonDropTarget.java
+++ b/src/com/android/launcher2/ButtonDropTarget.java
@@ -18,7 +18,8 @@ package com.android.launcher2;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.TextView;
@@ -41,7 +42,7 @@ public class ButtonDropTarget extends TextView implements DropTarget, DragContro
protected boolean mActive;
/** The paint applied to the drag view on hover */
- protected final Paint mHoverPaint = new Paint();
+ protected int mHoverColor = 0;
public ButtonDropTarget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -70,8 +71,12 @@ public class ButtonDropTarget extends TextView implements DropTarget, DragContro
public void onDrop(DragObject d) {
}
+ public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+ // Do nothing
+ }
+
public void onDragEnter(DragObject d) {
- d.dragView.setPaint(mHoverPaint);
+ d.dragView.setColor(mHoverColor);
}
public void onDragOver(DragObject d) {
@@ -79,7 +84,7 @@ public class ButtonDropTarget extends TextView implements DropTarget, DragContro
}
public void onDragExit(DragObject d) {
- d.dragView.setPaint(null);
+ d.dragView.setColor(0);
}
public void onDragStart(DragSource source, Object info, int dragAction) {
@@ -100,6 +105,26 @@ public class ButtonDropTarget extends TextView implements DropTarget, DragContro
outRect.bottom += mBottomDragPadding;
}
+ Rect getIconRect(int itemWidth, int itemHeight, int drawableWidth, int drawableHeight) {
+ DragLayer dragLayer = mLauncher.getDragLayer();
+
+ // Find the rect to animate to (the view is center aligned)
+ Rect to = new Rect();
+ dragLayer.getViewRectRelativeToSelf(this, to);
+ int width = drawableWidth;
+ int height = drawableHeight;
+ int left = to.left + getPaddingLeft();
+ int top = to.top + (getMeasuredHeight() - height) / 2;
+ to.set(left, top, left + width, top + height);
+
+ // Center the destination rect about the trash icon
+ int xOffset = (int) -(itemWidth - width) / 2;
+ int yOffset = (int) -(itemHeight - height) / 2;
+ to.offset(xOffset, yOffset);
+
+ return to;
+ }
+
@Override
public DropTarget getDropTargetDelegate(DragObject d) {
return null;
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index 8aae809b9..199c41a59 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -18,8 +18,6 @@ package com.android.launcher2;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -28,13 +26,14 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
-import android.graphics.RectF;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.util.AttributeSet;
@@ -53,10 +52,12 @@ import com.android.launcher2.FolderIcon.FolderRingAnimator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.Stack;
public class CellLayout extends ViewGroup {
static final String TAG = "CellLayout";
+ private Launcher mLauncher;
private int mOriginalCellWidth;
private int mOriginalCellHeight;
private int mCellWidth;
@@ -83,6 +84,7 @@ public class CellLayout extends ViewGroup {
int[] mTempLocation = new int[2];
boolean[][] mOccupied;
+ boolean[][] mTmpOccupied;
private boolean mLastDownOnOccupiedCell = false;
private OnTouchListener mInterceptTouchListener;
@@ -109,7 +111,7 @@ public class CellLayout extends ViewGroup {
// These arrays are used to implement the drag visualization on x-large screens.
// They are used as circular arrays, indexed by mDragOutlineCurrent.
- private Point[] mDragOutlines = new Point[4];
+ private Rect[] mDragOutlines = new Rect[4];
private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
private InterruptibleInOutAnimator[] mDragOutlineAnims =
new InterruptibleInOutAnimator[mDragOutlines.length];
@@ -124,8 +126,12 @@ public class CellLayout extends ViewGroup {
private InterruptibleInOutAnimator mCrosshairsAnimator = null;
private float mCrosshairsVisibility = 0.0f;
- private HashMap<CellLayout.LayoutParams, ObjectAnimator> mReorderAnimators = new
- HashMap<CellLayout.LayoutParams, ObjectAnimator>();
+ private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
+ HashMap<CellLayout.LayoutParams, Animator>();
+ private HashMap<View, ReorderHintAnimation>
+ mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
+
+ private boolean mItemPlacementDirty = false;
// When a drag operation is in progress, holds the nearest cell to the touch point
private final int[] mDragCell = new int[2];
@@ -133,7 +139,28 @@ public class CellLayout extends ViewGroup {
private boolean mDragging = false;
private TimeInterpolator mEaseOutInterpolator;
- private CellLayoutChildren mChildren;
+ private ShortcutAndWidgetContainer mShortcutsAndWidgets;
+
+ private boolean mIsHotseat = false;
+ private float mChildScale = 1f;
+ private float mHotseatChildScale = 1f;
+
+ public static final int MODE_DRAG_OVER = 0;
+ public static final int MODE_ON_DROP = 1;
+ public static final int MODE_ON_DROP_EXTERNAL = 2;
+ public static final int MODE_ACCEPT_DROP = 3;
+ private static final boolean DESTRUCTIVE_REORDER = false;
+ private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
+
+ private static final float REORDER_HINT_MAGNITUDE = 0.27f;
+ private static final int REORDER_ANIMATION_DURATION = 150;
+ private float mReorderHintAnimationMagnitude;
+
+ private ArrayList<View> mIntersectingViews = new ArrayList<View>();
+ private Rect mOccupiedRect = new Rect();
+ private int[] mDirectionVector = new int[2];
+ int[] mPreviousReorderDirection = new int[2];
+ private static final int INVALID_DIRECTION = -100;
public CellLayout(Context context) {
this(context, null);
@@ -149,6 +176,7 @@ public class CellLayout extends ViewGroup {
// A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
// the user where a dragged item will land when dropped.
setWillNotDraw(false);
+ mLauncher = (Launcher) context;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
@@ -162,6 +190,9 @@ public class CellLayout extends ViewGroup {
mCountX = LauncherModel.getCellCountX();
mCountY = LauncherModel.getCellCountY();
mOccupied = new boolean[mCountX][mCountY];
+ mTmpOccupied = new boolean[mCountX][mCountY];
+ mPreviousReorderDirection[0] = INVALID_DIRECTION;
+ mPreviousReorderDirection[1] = INVALID_DIRECTION;
a.recycle();
@@ -177,9 +208,21 @@ public class CellLayout extends ViewGroup {
mForegroundPadding =
res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
+ mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
+ res.getDimensionPixelSize(R.dimen.app_icon_size));
+
mNormalBackground.setFilterBitmap(true);
mActiveGlowBackground.setFilterBitmap(true);
+ int iconScale = res.getInteger(R.integer.app_icon_scale_percent);
+ if (iconScale >= 0) {
+ mChildScale = iconScale / 100f;
+ }
+ int hotseatIconScale = res.getInteger(R.integer.app_icon_hotseat_scale_percent);
+ if (hotseatIconScale >= 0) {
+ mHotseatChildScale = hotseatIconScale / 100f;
+ }
+
// Initialize the data structures used for the drag visualization.
mCrosshairsDrawable = res.getDrawable(R.drawable.gardening_crosshairs);
@@ -198,7 +241,7 @@ public class CellLayout extends ViewGroup {
mDragCell[0] = mDragCell[1] = -1;
for (int i = 0; i < mDragOutlines.length; i++) {
- mDragOutlines[i] = new Point(-1, -1);
+ mDragOutlines[i] = new Rect(-1, -1, -1, -1);
}
// When dragging things around the home screens, we show a green outline of
@@ -232,10 +275,7 @@ public class CellLayout extends ViewGroup {
animation.cancel();
} else {
mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
- final int left = mDragOutlines[thisIndex].x;
- final int top = mDragOutlines[thisIndex].y;
- CellLayout.this.invalidate(left, top,
- left + outline.getWidth(), top + outline.getHeight());
+ CellLayout.this.invalidate(mDragOutlines[thisIndex]);
}
}
});
@@ -255,9 +295,9 @@ public class CellLayout extends ViewGroup {
mBackgroundRect = new Rect();
mForegroundRect = new Rect();
- mChildren = new CellLayoutChildren(context);
- mChildren.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
- addView(mChildren);
+ mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
+ addView(mShortcutsAndWidgets);
}
static int widthInPortrait(Resources r, int numCells) {
@@ -283,13 +323,15 @@ public class CellLayout extends ViewGroup {
}
public void enableHardwareLayers() {
- mChildren.enableHardwareLayers();
+ mShortcutsAndWidgets.enableHardwareLayers();
}
public void setGridSize(int x, int y) {
mCountX = x;
mCountY = y;
mOccupied = new boolean[mCountX][mCountY];
+ mTmpOccupied = new boolean[mCountX][mCountY];
+ mTempRectStack.clear();
requestLayout();
}
@@ -326,13 +368,6 @@ public class CellLayout extends ViewGroup {
}
}
- public CellLayoutChildren getChildrenLayout() {
- if (getChildCount() > 0) {
- return (CellLayoutChildren) getChildAt(0);
- }
- return null;
- }
-
void setIsDragOverlapping(boolean isDragOverlapping) {
if (mIsDragOverlapping != isDragOverlapping) {
mIsDragOverlapping = isDragOverlapping;
@@ -419,10 +454,10 @@ public class CellLayout extends ViewGroup {
for (int i = 0; i < mDragOutlines.length; i++) {
final float alpha = mDragOutlineAlphas[i];
if (alpha > 0) {
- final Point p = mDragOutlines[i];
+ final Rect r = mDragOutlines[i];
final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
paint.setAlpha((int)(alpha + .5f));
- canvas.drawBitmap(b, p.x, p.y, paint);
+ canvas.drawBitmap(b, null, r, paint);
}
}
@@ -439,6 +474,23 @@ public class CellLayout extends ViewGroup {
}
}
+ if (DEBUG_VISUALIZE_OCCUPIED) {
+ int[] pt = new int[2];
+ ColorDrawable cd = new ColorDrawable(Color.RED);
+ cd.setBounds(0, 0, 80, 80);
+ for (int i = 0; i < mCountX; i++) {
+ for (int j = 0; j < mCountY; j++) {
+ if (mOccupied[i][j]) {
+ cellToPoint(i, j, pt);
+ canvas.save();
+ canvas.translate(pt[0], pt[1]);
+ cd.draw(canvas);
+ canvas.restore();
+ }
+ }
+ }
+ }
+
// The folder outer / inner ring image(s)
for (int i = 0; i < mFolderOuterRings.size(); i++) {
FolderRingAnimator fra = mFolderOuterRings.get(i);
@@ -554,10 +606,63 @@ public class CellLayout extends ViewGroup {
return mCountY;
}
+ public void setIsHotseat(boolean isHotseat) {
+ mIsHotseat = isHotseat;
+ }
+
+ public float getChildrenScale() {
+ return mIsHotseat ? mHotseatChildScale : mChildScale;
+ }
+
public boolean addViewToCellLayout(
View child, int index, int childId, LayoutParams params, boolean markCells) {
+ return addViewToCellLayout(child, index, childId, params, markCells, false);
+ }
+
+ private void scaleChild(BubbleTextView bubbleChild, float pivot, float scale) {
+ // If we haven't measured the child yet, do it now
+ // (this happens if we're being dropped from all-apps
+ if (bubbleChild.getLayoutParams() instanceof LayoutParams &&
+ (bubbleChild.getMeasuredWidth() | bubbleChild.getMeasuredHeight()) == 0) {
+ getShortcutsAndWidgets().measureChild(bubbleChild);
+ }
+
+ bubbleChild.setScaleX(scale);
+ bubbleChild.setScaleY(scale);
+ }
+
+ private void resetChild(BubbleTextView bubbleChild) {
+ bubbleChild.setScaleX(1f);
+ bubbleChild.setScaleY(1f);
+
+ bubbleChild.setTextColor(getResources().getColor(R.color.workspace_icon_text_color));
+ }
+
+ public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
+ boolean markCells, boolean allApps) {
final LayoutParams lp = params;
+ // Hotseat icons - scale down and remove text
+ // Don't scale the all apps button
+ // scale percent set to -1 means do not scale
+ // Only scale BubbleTextViews
+ if (child instanceof BubbleTextView) {
+ BubbleTextView bubbleChild = (BubbleTextView) child;
+
+ // Start the child with 100% scale and visible text
+ resetChild(bubbleChild);
+
+ if (mIsHotseat && !allApps && mHotseatChildScale >= 0) {
+ // Scale/make transparent for a hotseat
+ scaleChild(bubbleChild, 0f, mHotseatChildScale);
+
+ bubbleChild.setTextColor(getResources().getColor(android.R.color.transparent));
+ } else if (mChildScale >= 0) {
+ // Else possibly still scale it if we need to for smaller icons
+ scaleChild(bubbleChild, 0f, mChildScale);
+ }
+ }
+
// Generate an id for each view, this assumes we have at most 256x256 cells
// per workspace screen
if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
@@ -568,7 +673,7 @@ public class CellLayout extends ViewGroup {
child.setId(childId);
- mChildren.addView(child, index, lp);
+ mShortcutsAndWidgets.addView(child, index, lp);
if (markCells) markCellsAsOccupiedForView(child);
@@ -580,61 +685,53 @@ public class CellLayout extends ViewGroup {
@Override
public void removeAllViews() {
clearOccupiedCells();
- mChildren.removeAllViews();
+ mShortcutsAndWidgets.removeAllViews();
}
@Override
public void removeAllViewsInLayout() {
- if (mChildren.getChildCount() > 0) {
+ if (mShortcutsAndWidgets.getChildCount() > 0) {
clearOccupiedCells();
- mChildren.removeAllViewsInLayout();
+ mShortcutsAndWidgets.removeAllViewsInLayout();
}
}
public void removeViewWithoutMarkingCells(View view) {
- mChildren.removeView(view);
+ mShortcutsAndWidgets.removeView(view);
}
@Override
public void removeView(View view) {
markCellsAsUnoccupiedForView(view);
- mChildren.removeView(view);
+ mShortcutsAndWidgets.removeView(view);
}
@Override
public void removeViewAt(int index) {
- markCellsAsUnoccupiedForView(mChildren.getChildAt(index));
- mChildren.removeViewAt(index);
+ markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
+ mShortcutsAndWidgets.removeViewAt(index);
}
@Override
public void removeViewInLayout(View view) {
markCellsAsUnoccupiedForView(view);
- mChildren.removeViewInLayout(view);
+ mShortcutsAndWidgets.removeViewInLayout(view);
}
@Override
public void removeViews(int start, int count) {
for (int i = start; i < start + count; i++) {
- markCellsAsUnoccupiedForView(mChildren.getChildAt(i));
+ markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
}
- mChildren.removeViews(start, count);
+ mShortcutsAndWidgets.removeViews(start, count);
}
@Override
public void removeViewsInLayout(int start, int count) {
for (int i = start; i < start + count; i++) {
- markCellsAsUnoccupiedForView(mChildren.getChildAt(i));
+ markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
}
- mChildren.removeViewsInLayout(start, count);
- }
-
- public void drawChildren(Canvas canvas) {
- mChildren.draw(canvas);
- }
-
- void buildChildrenLayer() {
- mChildren.buildLayer();
+ mShortcutsAndWidgets.removeViewsInLayout(start, count);
}
@Override
@@ -645,24 +742,29 @@ public class CellLayout extends ViewGroup {
public void setTagToCellInfoForPoint(int touchX, int touchY) {
final CellInfo cellInfo = mCellInfo;
- final Rect frame = mRect;
+ Rect frame = mRect;
final int x = touchX + mScrollX;
final int y = touchY + mScrollY;
- final int count = mChildren.getChildCount();
+ final int count = mShortcutsAndWidgets.getChildCount();
boolean found = false;
for (int i = count - 1; i >= 0; i--) {
- final View child = mChildren.getChildAt(i);
+ final View child = mShortcutsAndWidgets.getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
lp.isLockedToGrid) {
child.getHitRect(frame);
+ float scale = child.getScaleX();
+ frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
+ child.getBottom());
// The child hit rect is relative to the CellLayoutChildren parent, so we need to
// offset that by this CellLayout's padding to test an (x,y) point that is relative
// to this view.
frame.offset(mPaddingLeft, mPaddingTop);
+ frame.inset((int) (frame.width() * (1f - scale) / 2),
+ (int) (frame.height() * (1f - scale) / 2));
if (frame.contains(x, y)) {
cellInfo.cell = child;
@@ -709,6 +811,7 @@ public class CellLayout extends ViewGroup {
if (action == MotionEvent.ACTION_DOWN) {
setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
}
+
return false;
}
@@ -775,7 +878,7 @@ public class CellLayout extends ViewGroup {
}
/**
- * Given a cell coordinate, return the point that represents the upper left corner of that cell
+ * Given a cell coordinate, return the point that represents the center of the cell
*
* @param cellX X coordinate of the cell
* @param cellY Y coordinate of the cell
@@ -783,11 +886,47 @@ public class CellLayout extends ViewGroup {
* @param result Array of 2 ints to hold the x and y coordinate of the point
*/
void cellToCenterPoint(int cellX, int cellY, int[] result) {
+ regionToCenterPoint(cellX, cellY, 1, 1, result);
+ }
+
+ /**
+ * Given a cell coordinate and span return the point that represents the center of the regio
+ *
+ * @param cellX X coordinate of the cell
+ * @param cellY Y coordinate of the cell
+ *
+ * @param result Array of 2 ints to hold the x and y coordinate of the point
+ */
+ void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
+ final int hStartPadding = getPaddingLeft();
+ final int vStartPadding = getPaddingTop();
+ result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
+ (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
+ result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
+ (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
+ }
+
+ /**
+ * Given a cell coordinate and span fills out a corresponding pixel rect
+ *
+ * @param cellX X coordinate of the cell
+ * @param cellY Y coordinate of the cell
+ * @param result Rect in which to write the result
+ */
+ void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
final int hStartPadding = getPaddingLeft();
final int vStartPadding = getPaddingTop();
+ final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
+ final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
+ result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
+ top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
+ }
- result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) + mCellWidth / 2;
- result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) + mCellHeight / 2;
+ public float getDistanceFromCell(float x, float y, int[] cell) {
+ cellToCenterPoint(cell[0], cell[1], mTmpPoint);
+ float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
+ Math.pow(y - mTmpPoint[1], 2));
+ return distance;
}
int getCellWidth() {
@@ -842,7 +981,7 @@ public class CellLayout extends ViewGroup {
int vFreeSpace = vSpace - (mCountY * mOriginalCellHeight);
mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
- mChildren.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
} else {
mWidthGap = mOriginalWidthGap;
mHeightGap = mOriginalHeightGap;
@@ -891,22 +1030,18 @@ public class CellLayout extends ViewGroup {
@Override
protected void setChildrenDrawingCacheEnabled(boolean enabled) {
- mChildren.setChildrenDrawingCacheEnabled(enabled);
+ mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
}
@Override
protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
- mChildren.setChildrenDrawnWithCacheEnabled(enabled);
+ mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
}
public float getBackgroundAlpha() {
return mBackgroundAlpha;
}
- public void setFastBackgroundAlpha(float alpha) {
- mBackgroundAlpha = alpha;
- }
-
public void setBackgroundAlphaMultiplier(float multiplier) {
mBackgroundAlphaMultiplier = multiplier;
}
@@ -916,50 +1051,39 @@ public class CellLayout extends ViewGroup {
}
public void setBackgroundAlpha(float alpha) {
- mBackgroundAlpha = alpha;
- invalidate();
- }
-
- // Need to return true to let the view system know we know how to handle alpha-- this is
- // because when our children have an alpha of 0.0f, they are still rendering their "dimmed"
- // versions
- @Override
- protected boolean onSetAlpha(int alpha) {
- return true;
- }
-
- public void setAlpha(float alpha) {
- setChildrenAlpha(alpha);
- super.setAlpha(alpha);
- }
-
- public void setFastAlpha(float alpha) {
- setFastChildrenAlpha(alpha);
- super.setFastAlpha(alpha);
+ if (mBackgroundAlpha != alpha) {
+ mBackgroundAlpha = alpha;
+ invalidate();
+ }
}
- private void setChildrenAlpha(float alpha) {
+ public void setShortcutAndWidgetAlpha(float alpha) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
getChildAt(i).setAlpha(alpha);
}
}
- private void setFastChildrenAlpha(float alpha) {
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- getChildAt(i).setFastAlpha(alpha);
+ public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
+ if (getChildCount() > 0) {
+ return (ShortcutAndWidgetContainer) getChildAt(0);
}
+ return null;
}
public View getChildAt(int x, int y) {
- return mChildren.getChildAt(x, y);
+ return mShortcutsAndWidgets.getChildAt(x, y);
}
public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
- int delay) {
- CellLayoutChildren clc = getChildrenLayout();
- if (clc.indexOfChild(child) != -1 && !mOccupied[cellX][cellY]) {
+ int delay, boolean permanent, boolean adjustOccupied) {
+ ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
+ boolean[][] occupied = mOccupied;
+ if (!permanent) {
+ occupied = mTmpOccupied;
+ }
+
+ if (clc.indexOfChild(child) != -1) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final ItemInfo info = (ItemInfo) child.getTag();
@@ -969,34 +1093,48 @@ public class CellLayout extends ViewGroup {
mReorderAnimators.remove(lp);
}
- int oldX = lp.x;
- int oldY = lp.y;
- mOccupied[lp.cellX][lp.cellY] = false;
- mOccupied[cellX][cellY] = true;
-
+ final int oldX = lp.x;
+ final int oldY = lp.y;
+ if (adjustOccupied) {
+ occupied[lp.cellX][lp.cellY] = false;
+ occupied[cellX][cellY] = true;
+ }
lp.isLockedToGrid = true;
- lp.cellX = info.cellX = cellX;
- lp.cellY = info.cellY = cellY;
+ if (permanent) {
+ lp.cellX = info.cellX = cellX;
+ lp.cellY = info.cellY = cellY;
+ } else {
+ lp.tmpCellX = cellX;
+ lp.tmpCellY = cellY;
+ }
clc.setupLp(lp);
lp.isLockedToGrid = false;
- int newX = lp.x;
- int newY = lp.y;
+ final int newX = lp.x;
+ final int newY = lp.y;
lp.x = oldX;
lp.y = oldY;
- child.requestLayout();
- PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", oldX, newX);
- PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", oldY, newY);
- ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, x, y);
- oa.setDuration(duration);
- mReorderAnimators.put(lp, oa);
- oa.addUpdateListener(new AnimatorUpdateListener() {
+ // Exit early if we're not actually moving the view
+ if (oldX == newX && oldY == newY) {
+ lp.isLockedToGrid = true;
+ return true;
+ }
+
+ ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
+ va.setDuration(duration);
+ mReorderAnimators.put(lp, va);
+
+ va.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
public void onAnimationUpdate(ValueAnimator animation) {
+ float r = ((Float) animation.getAnimatedValue()).floatValue();
+ lp.x = (int) ((1 - r) * oldX + r * newX);
+ lp.y = (int) ((1 - r) * oldY + r * newY);
child.requestLayout();
}
});
- oa.addListener(new AnimatorListenerAdapter() {
+ va.addListener(new AnimatorListenerAdapter() {
boolean cancelled = false;
public void onAnimationEnd(Animator animation) {
// If the animation was cancelled, it means that another animation
@@ -1004,6 +1142,7 @@ public class CellLayout extends ViewGroup {
// place just yet.
if (!cancelled) {
lp.isLockedToGrid = true;
+ child.requestLayout();
}
if (mReorderAnimators.containsKey(lp)) {
mReorderAnimators.remove(lp);
@@ -1013,8 +1152,8 @@ public class CellLayout extends ViewGroup {
cancelled = true;
}
});
- oa.setStartDelay(delay);
- oa.start();
+ va.setStartDelay(delay);
+ va.start();
return true;
}
return false;
@@ -1050,12 +1189,11 @@ public class CellLayout extends ViewGroup {
result[1] = Math.max(0, result[1]); // Snap to top
}
- void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY,
- int spanX, int spanY, Point dragOffset, Rect dragRegion) {
-
+ void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
+ int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
final int oldDragCellX = mDragCell[0];
final int oldDragCellY = mDragCell[1];
- final int[] nearest = findNearestVacantArea(originX, originY, spanX, spanY, v, mDragCell);
+
if (v != null && dragOffset == null) {
mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
} else {
@@ -1069,10 +1207,12 @@ public class CellLayout extends ViewGroup {
return;
}
- if (nearest != null && (nearest[0] != oldDragCellX || nearest[1] != oldDragCellY)) {
+ if (cellX != oldDragCellX || cellY != oldDragCellY) {
+ mDragCell[0] = cellX;
+ mDragCell[1] = cellY;
// Find the top left corner of the rect the object will occupy
final int[] topLeft = mTmpPoint;
- cellToPoint(nearest[0], nearest[1], topLeft);
+ cellToPoint(cellX, cellY, topLeft);
int left = topLeft[0];
int top = topLeft[1];
@@ -1106,12 +1246,15 @@ public class CellLayout extends ViewGroup {
- dragOutline.getHeight()) / 2;
}
}
-
final int oldIndex = mDragOutlineCurrent;
mDragOutlineAnims[oldIndex].animateOut();
mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
+ Rect r = mDragOutlines[mDragOutlineCurrent];
+ r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
+ if (resize) {
+ cellToRect(cellX, cellY, spanX, spanY, r);
+ }
- mDragOutlines[mDragOutlineCurrent].set(left, top);
mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
mDragOutlineAnims[mDragOutlineCurrent].animateIn();
}
@@ -1125,8 +1268,7 @@ public class CellLayout extends ViewGroup {
public void clearDragOutlines() {
final int oldIndex = mDragOutlineCurrent;
mDragOutlineAnims[oldIndex].animateOut();
- mDragCell[0] = -1;
- mDragCell[1] = -1;
+ mDragCell[0] = mDragCell[1] = -1;
}
/**
@@ -1142,8 +1284,8 @@ public class CellLayout extends ViewGroup {
* @return The X, Y cell of a vacant area that can contain this object,
* nearest the requested location.
*/
- int[] findNearestVacantArea(
- int pixelX, int pixelY, int spanX, int spanY, int[] result) {
+ int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
+ int[] result) {
return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
}
@@ -1153,6 +1295,27 @@ public class CellLayout extends ViewGroup {
*
* @param pixelX The X location at which you want to search for a vacant area.
* @param pixelY The Y location at which you want to search for a vacant area.
+ * @param minSpanX The minimum horizontal span required
+ * @param minSpanY The minimum vertical span required
+ * @param spanX Horizontal span of the object.
+ * @param spanY Vertical span of the object.
+ * @param result Array in which to place the result, or null (in which case a new array will
+ * be allocated)
+ * @return The X, Y cell of a vacant area that can contain this object,
+ * nearest the requested location.
+ */
+ int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
+ int spanY, int[] result, int[] resultSpan) {
+ return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
+ result, resultSpan);
+ }
+
+ /**
+ * Find a vacant area that will fit the given bounds nearest the requested
+ * cell location. Uses Euclidean distance to score multiple vacant areas.
+ *
+ * @param pixelX The X location at which you want to search for a vacant area.
+ * @param pixelY The Y location at which you want to search for a vacant area.
* @param spanX Horizontal span of the object.
* @param spanY Vertical span of the object.
* @param ignoreOccupied If true, the result can be an occupied cell
@@ -1163,8 +1326,47 @@ public class CellLayout extends ViewGroup {
*/
int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
boolean ignoreOccupied, int[] result) {
+ return findNearestArea(pixelX, pixelY, spanX, spanY,
+ spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
+ }
+
+ private final Stack<Rect> mTempRectStack = new Stack<Rect>();
+ private void lazyInitTempRectStack() {
+ if (mTempRectStack.isEmpty()) {
+ for (int i = 0; i < mCountX * mCountY; i++) {
+ mTempRectStack.push(new Rect());
+ }
+ }
+ }
+
+ private void recycleTempRects(Stack<Rect> used) {
+ while (!used.isEmpty()) {
+ mTempRectStack.push(used.pop());
+ }
+ }
+
+ /**
+ * Find a vacant area that will fit the given bounds nearest the requested
+ * cell location. Uses Euclidean distance to score multiple vacant areas.
+ *
+ * @param pixelX The X location at which you want to search for a vacant area.
+ * @param pixelY The Y location at which you want to search for a vacant area.
+ * @param minSpanX The minimum horizontal span required
+ * @param minSpanY The minimum vertical span required
+ * @param spanX Horizontal span of the object.
+ * @param spanY Vertical span of the object.
+ * @param ignoreOccupied If true, the result can be an occupied cell
+ * @param result Array in which to place the result, or null (in which case a new array will
+ * be allocated)
+ * @return The X, Y cell of a vacant area that can contain this object,
+ * nearest the requested location.
+ */
+ int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
+ View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
+ boolean[][] occupied) {
+ lazyInitTempRectStack();
// mark space take by ignoreView as available (method checks if ignoreView is null)
- markCellsAsUnoccupiedForView(ignoreView);
+ markCellsAsUnoccupiedForView(ignoreView, occupied);
// For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
// to the center of the item, but we are searching based on the top-left cell, so
@@ -1175,49 +1377,956 @@ public class CellLayout extends ViewGroup {
// Keep track of best-scoring drop area
final int[] bestXY = result != null ? result : new int[2];
double bestDistance = Double.MAX_VALUE;
+ final Rect bestRect = new Rect(-1, -1, -1, -1);
+ final Stack<Rect> validRegions = new Stack<Rect>();
final int countX = mCountX;
final int countY = mCountY;
- final boolean[][] occupied = mOccupied;
- for (int y = 0; y < countY - (spanY - 1); y++) {
+ if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
+ spanX < minSpanX || spanY < minSpanY) {
+ return bestXY;
+ }
+
+ for (int y = 0; y < countY - (minSpanY - 1); y++) {
inner:
- for (int x = 0; x < countX - (spanX - 1); x++) {
+ for (int x = 0; x < countX - (minSpanX - 1); x++) {
+ int ySize = -1;
+ int xSize = -1;
if (ignoreOccupied) {
- for (int i = 0; i < spanX; i++) {
- for (int j = 0; j < spanY; j++) {
+ // First, let's see if this thing fits anywhere
+ for (int i = 0; i < minSpanX; i++) {
+ for (int j = 0; j < minSpanY; j++) {
if (occupied[x + i][y + j]) {
- // small optimization: we can skip to after the column we
- // just found an occupied cell
- x += i;
continue inner;
}
}
}
+ xSize = minSpanX;
+ ySize = minSpanY;
+
+ // We know that the item will fit at _some_ acceptable size, now let's see
+ // how big we can make it. We'll alternate between incrementing x and y spans
+ // until we hit a limit.
+ boolean incX = true;
+ boolean hitMaxX = xSize >= spanX;
+ boolean hitMaxY = ySize >= spanY;
+ while (!(hitMaxX && hitMaxY)) {
+ if (incX && !hitMaxX) {
+ for (int j = 0; j < ySize; j++) {
+ if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
+ // We can't move out horizontally
+ hitMaxX = true;
+ }
+ }
+ if (!hitMaxX) {
+ xSize++;
+ }
+ } else if (!hitMaxY) {
+ for (int i = 0; i < xSize; i++) {
+ if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
+ // We can't move out vertically
+ hitMaxY = true;
+ }
+ }
+ if (!hitMaxY) {
+ ySize++;
+ }
+ }
+ hitMaxX |= xSize >= spanX;
+ hitMaxY |= ySize >= spanY;
+ incX = !incX;
+ }
+ incX = true;
+ hitMaxX = xSize >= spanX;
+ hitMaxY = ySize >= spanY;
}
final int[] cellXY = mTmpXY;
cellToCenterPoint(x, y, cellXY);
+ // We verify that the current rect is not a sub-rect of any of our previous
+ // candidates. In this case, the current rect is disqualified in favour of the
+ // containing rect.
+ Rect currentRect = mTempRectStack.pop();
+ currentRect.set(x, y, x + xSize, y + ySize);
+ boolean contained = false;
+ for (Rect r : validRegions) {
+ if (r.contains(currentRect)) {
+ contained = true;
+ break;
+ }
+ }
+ validRegions.push(currentRect);
double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
+ Math.pow(cellXY[1] - pixelY, 2));
- if (distance <= bestDistance) {
+
+ if ((distance <= bestDistance && !contained) ||
+ currentRect.contains(bestRect)) {
bestDistance = distance;
bestXY[0] = x;
bestXY[1] = y;
+ if (resultSpan != null) {
+ resultSpan[0] = xSize;
+ resultSpan[1] = ySize;
+ }
+ bestRect.set(currentRect);
}
}
}
// re-mark space taken by ignoreView as occupied
- markCellsAsOccupiedForView(ignoreView);
+ markCellsAsOccupiedForView(ignoreView, occupied);
// Return -1, -1 if no suitable location found
if (bestDistance == Double.MAX_VALUE) {
bestXY[0] = -1;
bestXY[1] = -1;
}
+ recycleTempRects(validRegions);
return bestXY;
}
+ /**
+ * Find a vacant area that will fit the given bounds nearest the requested
+ * cell location, and will also weigh in a suggested direction vector of the
+ * desired location. This method computers distance based on unit grid distances,
+ * not pixel distances.
+ *
+ * @param cellX The X cell nearest to which you want to search for a vacant area.
+ * @param cellY The Y cell nearest which you want to search for a vacant area.
+ * @param spanX Horizontal span of the object.
+ * @param spanY Vertical span of the object.
+ * @param direction The favored direction in which the views should move from x, y
+ * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
+ * matches exactly. Otherwise we find the best matching direction.
+ * @param occoupied The array which represents which cells in the CellLayout are occupied
+ * @param blockOccupied The array which represents which cells in the specified block (cellX,
+ * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
+ * @param result Array in which to place the result, or null (in which case a new array will
+ * be allocated)
+ * @return The X, Y cell of a vacant area that can contain this object,
+ * nearest the requested location.
+ */
+ private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
+ boolean[][] occupied, boolean blockOccupied[][], int[] result) {
+ // Keep track of best-scoring drop area
+ final int[] bestXY = result != null ? result : new int[2];
+ float bestDistance = Float.MAX_VALUE;
+ int bestDirectionScore = Integer.MIN_VALUE;
+
+ final int countX = mCountX;
+ final int countY = mCountY;
+
+ for (int y = 0; y < countY - (spanY - 1); y++) {
+ inner:
+ for (int x = 0; x < countX - (spanX - 1); x++) {
+ // First, let's see if this thing fits anywhere
+ for (int i = 0; i < spanX; i++) {
+ for (int j = 0; j < spanY; j++) {
+ if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
+ continue inner;
+ }
+ }
+ }
+
+ float distance = (float)
+ Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
+ int[] curDirection = mTmpPoint;
+ computeDirectionVector(x - cellX, y - cellY, curDirection);
+ // The direction score is just the dot product of the two candidate direction
+ // and that passed in.
+ int curDirectionScore = direction[0] * curDirection[0] +
+ direction[1] * curDirection[1];
+ boolean exactDirectionOnly = false;
+ boolean directionMatches = direction[0] == curDirection[0] &&
+ direction[0] == curDirection[0];
+ if ((directionMatches || !exactDirectionOnly) &&
+ Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
+ bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
+ bestDistance = distance;
+ bestDirectionScore = curDirectionScore;
+ bestXY[0] = x;
+ bestXY[1] = y;
+ }
+ }
+ }
+
+ // Return -1, -1 if no suitable location found
+ if (bestDistance == Float.MAX_VALUE) {
+ bestXY[0] = -1;
+ bestXY[1] = -1;
+ }
+ return bestXY;
+ }
+
+ private int[] findNearestAreaInDirection(int cellX, int cellY, int spanX, int spanY,
+ int[] direction,boolean[][] occupied,
+ boolean blockOccupied[][], int[] result) {
+ // Keep track of best-scoring drop area
+ final int[] bestXY = result != null ? result : new int[2];
+ bestXY[0] = -1;
+ bestXY[1] = -1;
+ float bestDistance = Float.MAX_VALUE;
+
+ // We use this to march in a single direction
+ if ((direction[0] != 0 && direction[1] != 0) ||
+ (direction[0] == 0 && direction[1] == 0)) {
+ return bestXY;
+ }
+
+ // This will only incrememnet one of x or y based on the assertion above
+ int x = cellX + direction[0];
+ int y = cellY + direction[1];
+ while (x >= 0 && x + spanX <= mCountX && y >= 0 && y + spanY <= mCountY) {
+
+ boolean fail = false;
+ for (int i = 0; i < spanX; i++) {
+ for (int j = 0; j < spanY; j++) {
+ if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
+ fail = true;
+ }
+ }
+ }
+ if (!fail) {
+ float distance = (float)
+ Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
+ if (Float.compare(distance, bestDistance) < 0) {
+ bestDistance = distance;
+ bestXY[0] = x;
+ bestXY[1] = y;
+ }
+ }
+ x += direction[0];
+ y += direction[1];
+ }
+ return bestXY;
+ }
+
+ private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
+ int[] direction, ItemConfiguration currentState) {
+ CellAndSpan c = currentState.map.get(v);
+ boolean success = false;
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
+ markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
+
+ findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
+
+ if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
+ c.x = mTempLocation[0];
+ c.y = mTempLocation[1];
+ success = true;
+
+ }
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
+ return success;
+ }
+
+ // This method looks in the specified direction to see if there is an additional view
+ // immediately adjecent in that direction
+ private boolean addViewInDirection(ArrayList<View> views, Rect boundingRect, int[] direction,
+ boolean[][] occupied, View dragView, ItemConfiguration currentState) {
+ boolean found = false;
+
+ int childCount = mShortcutsAndWidgets.getChildCount();
+ Rect r0 = new Rect(boundingRect);
+ Rect r1 = new Rect();
+
+ int deltaX = 0;
+ int deltaY = 0;
+ if (direction[1] < 0) {
+ r0.set(r0.left, r0.top - 1, r0.right, r0.bottom);
+ deltaY = -1;
+ } else if (direction[1] > 0) {
+ r0.set(r0.left, r0.top, r0.right, r0.bottom + 1);
+ deltaY = 1;
+ } else if (direction[0] < 0) {
+ r0.set(r0.left - 1, r0.top, r0.right, r0.bottom);
+ deltaX = -1;
+ } else if (direction[0] > 0) {
+ r0.set(r0.left, r0.top, r0.right + 1, r0.bottom);
+ deltaX = 1;
+ }
+
+ for (int i = 0; i < childCount; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ if (views.contains(child) || child == dragView) continue;
+ CellAndSpan c = currentState.map.get(child);
+
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+ if (Rect.intersects(r0, r1)) {
+ if (!lp.canReorder) {
+ return false;
+ }
+ boolean pushed = false;
+ for (int x = c.x; x < c.x + c.spanX; x++) {
+ for (int y = c.y; y < c.y + c.spanY; y++) {
+ boolean inBounds = x - deltaX >= 0 && x -deltaX < mCountX
+ && y - deltaY >= 0 && y - deltaY < mCountY;
+ if (inBounds && occupied[x - deltaX][y - deltaY]) {
+ pushed = true;
+ }
+ }
+ }
+ if (pushed) {
+ views.add(child);
+ boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+ found = true;
+ }
+ }
+ }
+ return found;
+ }
+
+ private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
+ int[] direction, boolean push, View dragView, ItemConfiguration currentState) {
+ if (views.size() == 0) return true;
+
+ boolean success = false;
+ Rect boundingRect = null;
+ // We construct a rect which represents the entire group of views passed in
+ for (View v: views) {
+ CellAndSpan c = currentState.map.get(v);
+ if (boundingRect == null) {
+ boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+ } else {
+ boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ ArrayList<View> dup = (ArrayList<View>) views.clone();
+ // We try and expand the group of views in the direction vector passed, based on
+ // whether they are physically adjacent, ie. based on "push mechanics".
+ while (push && addViewInDirection(dup, boundingRect, direction, mTmpOccupied, dragView,
+ currentState)) {
+ }
+
+ // Mark the occupied state as false for the group of views we want to move.
+ for (View v: dup) {
+ CellAndSpan c = currentState.map.get(v);
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
+ }
+
+ boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
+ int top = boundingRect.top;
+ int left = boundingRect.left;
+ // We mark more precisely which parts of the bounding rect are truly occupied, allowing
+ // for tetris-style interlocking.
+ for (View v: dup) {
+ CellAndSpan c = currentState.map.get(v);
+ markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
+ }
+
+ markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
+
+ if (push) {
+ findNearestAreaInDirection(boundingRect.left, boundingRect.top, boundingRect.width(),
+ boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
+ } else {
+ findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
+ boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
+ }
+
+ // If we successfuly found a location by pushing the block of views, we commit it
+ if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
+ int deltaX = mTempLocation[0] - boundingRect.left;
+ int deltaY = mTempLocation[1] - boundingRect.top;
+ for (View v: dup) {
+ CellAndSpan c = currentState.map.get(v);
+ c.x += deltaX;
+ c.y += deltaY;
+ }
+ success = true;
+ }
+
+ // In either case, we set the occupied array as marked for the location of the views
+ for (View v: dup) {
+ CellAndSpan c = currentState.map.get(v);
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
+ }
+ return success;
+ }
+
+ private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
+ markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
+ }
+
+ private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
+ View ignoreView, ItemConfiguration solution) {
+
+ mIntersectingViews.clear();
+ mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
+
+ // Mark the desired location of the view currently being dragged.
+ if (ignoreView != null) {
+ CellAndSpan c = solution.map.get(ignoreView);
+ if (c != null) {
+ c.x = cellX;
+ c.y = cellY;
+ }
+ }
+ Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
+ Rect r1 = new Rect();
+ for (View child: solution.map.keySet()) {
+ if (child == ignoreView) continue;
+ CellAndSpan c = solution.map.get(child);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+ if (Rect.intersects(r0, r1)) {
+ if (!lp.canReorder) {
+ return false;
+ }
+ mIntersectingViews.add(child);
+ }
+ }
+
+ // We try to move the intersecting views as a block using the push mechanic
+ if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, true, ignoreView,
+ solution)) {
+ return true;
+ }
+ // Try the opposite direction
+ direction[0] *= -1;
+ direction[1] *= -1;
+ if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, true, ignoreView,
+ solution)) {
+ return true;
+ }
+ // Switch the direction back
+ direction[0] *= -1;
+ direction[1] *= -1;
+
+ // Next we try moving the views as a block , but without requiring the push mechanic
+ if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, false, ignoreView,
+ solution)) {
+ return true;
+ }
+
+ // Ok, they couldn't move as a block, let's move them individually
+ for (View v : mIntersectingViews) {
+ if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /*
+ * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
+ * the provided point and the provided cell
+ */
+ private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
+ double angle = Math.atan(((float) deltaY) / deltaX);
+
+ result[0] = 0;
+ result[1] = 0;
+ if (Math.abs(Math.cos(angle)) > 0.5f) {
+ result[0] = (int) Math.signum(deltaX);
+ }
+ if (Math.abs(Math.sin(angle)) > 0.5f) {
+ result[1] = (int) Math.signum(deltaY);
+ }
+ }
+
+ private void copyOccupiedArray(boolean[][] occupied) {
+ for (int i = 0; i < mCountX; i++) {
+ for (int j = 0; j < mCountY; j++) {
+ occupied[i][j] = mOccupied[i][j];
+ }
+ }
+ }
+
+ ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
+ int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
+ // Copy the current state into the solution. This solution will be manipulated as necessary.
+ copyCurrentStateToSolution(solution, false);
+ // Copy the current occupied array into the temporary occupied array. This array will be
+ // manipulated as necessary to find a solution.
+ copyOccupiedArray(mTmpOccupied);
+
+ // We find the nearest cell into which we would place the dragged item, assuming there's
+ // nothing in its way.
+ int result[] = new int[2];
+ result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
+
+ boolean success = false;
+ // First we try the exact nearest position of the item being dragged,
+ // we will then want to try to move this around to other neighbouring positions
+ success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
+ solution);
+
+ if (!success) {
+ // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
+ // x, then 1 in y etc.
+ if (spanX > minSpanX && (minSpanY == spanY || decX)) {
+ return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
+ dragView, false, solution);
+ } else if (spanY > minSpanY) {
+ return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
+ dragView, true, solution);
+ }
+ solution.isSolution = false;
+ } else {
+ solution.isSolution = true;
+ solution.dragViewX = result[0];
+ solution.dragViewY = result[1];
+ solution.dragViewSpanX = spanX;
+ solution.dragViewSpanY = spanY;
+ }
+ return solution;
+ }
+
+ private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
+ int childCount = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ CellAndSpan c;
+ if (temp) {
+ c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
+ } else {
+ c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
+ }
+ solution.map.put(child, c);
+ }
+ }
+
+ private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
+ for (int i = 0; i < mCountX; i++) {
+ for (int j = 0; j < mCountY; j++) {
+ mTmpOccupied[i][j] = false;
+ }
+ }
+
+ int childCount = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ if (child == dragView) continue;
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ CellAndSpan c = solution.map.get(child);
+ if (c != null) {
+ lp.tmpCellX = c.x;
+ lp.tmpCellY = c.y;
+ lp.cellHSpan = c.spanX;
+ lp.cellVSpan = c.spanY;
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
+ }
+ }
+ markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
+ solution.dragViewSpanY, mTmpOccupied, true);
+ }
+
+ private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
+ commitDragView) {
+
+ boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
+ for (int i = 0; i < mCountX; i++) {
+ for (int j = 0; j < mCountY; j++) {
+ occupied[i][j] = false;
+ }
+ }
+
+ int childCount = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ if (child == dragView) continue;
+ CellAndSpan c = solution.map.get(child);
+ if (c != null) {
+ animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
+ DESTRUCTIVE_REORDER, false);
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
+ }
+ }
+ if (commitDragView) {
+ markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
+ solution.dragViewSpanY, occupied, true);
+ }
+ }
+
+ // This method starts or changes the reorder hint animations
+ private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
+ int childCount = mShortcutsAndWidgets.getChildCount();
+ int timeForPriorAnimationToComplete = getMaxCompletionTime();
+ for (int i = 0; i < childCount; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ if (child == dragView) continue;
+ CellAndSpan c = solution.map.get(child);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (c != null) {
+ ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
+ c.x, c.y, c.spanX, c.spanY);
+ rha.animate(timeForPriorAnimationToComplete);
+ }
+ }
+ }
+
+ // Class which represents the reorder hint animations. These animations show that an item is
+ // in a temporary state, and hint at where the item will return to.
+ class ReorderHintAnimation {
+ View child;
+ float deltaX;
+ float deltaY;
+ private static final int DURATION = 140;
+ private int repeatCount;
+ private boolean cancelOnCycleComplete = false;
+ ValueAnimator va;
+
+ public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
+ int spanX, int spanY) {
+ regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
+ final int x0 = mTmpPoint[0];
+ final int y0 = mTmpPoint[1];
+ regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
+ final int x1 = mTmpPoint[0];
+ final int y1 = mTmpPoint[1];
+ final int dX = x1 - x0;
+ final int dY = y1 - y0;
+ deltaX = 0;
+ deltaY = 0;
+ if (dX == dY && dX == 0) {
+ } else {
+ if (dY == 0) {
+ deltaX = mReorderHintAnimationMagnitude;
+ } else if (dX == 0) {
+ deltaY = mReorderHintAnimationMagnitude;
+ } else {
+ double angle = Math.atan( (float) (dY) / dX);
+ deltaX = (int) (Math.cos(angle) * mReorderHintAnimationMagnitude);
+ deltaY = (int) (Math.sin(angle) * mReorderHintAnimationMagnitude);
+ }
+ }
+ this.child = child;
+ }
+
+ void animate(int delay) {
+ if (mShakeAnimators.containsKey(child)) {
+ ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
+ oldAnimation.completeAnimation();
+ mShakeAnimators.remove(child);
+ }
+ if (deltaX == 0 && deltaY == 0) {
+ return;
+ }
+ va = ValueAnimator.ofFloat(0f, 1f);
+ va.setRepeatMode(ValueAnimator.REVERSE);
+ va.setRepeatCount(ValueAnimator.INFINITE);
+ va.setDuration(DURATION);
+ va.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float r = ((Float) animation.getAnimatedValue()).floatValue();
+ float x = r * deltaX;
+ float y = r * deltaY;
+ child.setTranslationX(x);
+ child.setTranslationY(y);
+ }
+ });
+ va.addListener(new AnimatorListenerAdapter() {
+ public void onAnimationRepeat(Animator animation) {
+ repeatCount++;
+ // We make sure to end only after a full period
+ if (cancelOnCycleComplete && repeatCount % 2 == 0) {
+ va.cancel();
+ }
+ }
+ });
+ va.setStartDelay(Math.max(REORDER_ANIMATION_DURATION, delay));
+ mShakeAnimators.put(child, this);
+ va.start();
+ }
+
+
+ private void completeAnimation() {
+ cancelOnCycleComplete = true;
+ }
+
+ // Returns the time required to complete the current oscillating animation
+ private int completionTime() {
+ if (repeatCount % 2 == 0) {
+ return (int) (va.getDuration() - va.getCurrentPlayTime() + DURATION);
+ } else {
+ return (int) (va.getDuration() - va.getCurrentPlayTime());
+ }
+ }
+ }
+
+ private void completeAndClearReorderHintAnimations() {
+ for (ReorderHintAnimation a: mShakeAnimators.values()) {
+ a.completeAnimation();
+ }
+ mShakeAnimators.clear();
+ }
+
+ private int getMaxCompletionTime() {
+ int maxTime = 0;
+ for (ReorderHintAnimation a: mShakeAnimators.values()) {
+ maxTime = Math.max(maxTime, a.completionTime());
+ }
+ return maxTime;
+ }
+
+ private void commitTempPlacement() {
+ for (int i = 0; i < mCountX; i++) {
+ for (int j = 0; j < mCountY; j++) {
+ mOccupied[i][j] = mTmpOccupied[i][j];
+ }
+ }
+ int childCount = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ ItemInfo info = (ItemInfo) child.getTag();
+ // We do a null check here because the item info can be null in the case of the
+ // AllApps button in the hotseat.
+ if (info != null) {
+ info.cellX = lp.cellX = lp.tmpCellX;
+ info.cellY = lp.cellY = lp.tmpCellY;
+ }
+ }
+ mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
+ }
+
+ public void setUseTempCoords(boolean useTempCoords) {
+ int childCount = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
+ lp.useTmpCoords = useTempCoords;
+ }
+ }
+
+ ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
+ int spanX, int spanY, View dragView, ItemConfiguration solution) {
+ int[] result = new int[2];
+ int[] resultSpan = new int[2];
+ findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
+ resultSpan);
+ if (result[0] >= 0 && result[1] >= 0) {
+ copyCurrentStateToSolution(solution, false);
+ solution.dragViewX = result[0];
+ solution.dragViewY = result[1];
+ solution.dragViewSpanX = resultSpan[0];
+ solution.dragViewSpanY = resultSpan[1];
+ solution.isSolution = true;
+ } else {
+ solution.isSolution = false;
+ }
+ return solution;
+ }
+
+ public void prepareChildForDrag(View child) {
+ markCellsAsUnoccupiedForView(child);
+ }
+
+ /* This seems like it should be obvious and straight-forward, but when the direction vector
+ needs to match with the notion of the dragView pushing other views, we have to employ
+ a slightly more subtle notion of the direction vector. The question is what two points is
+ the vector between? The center of the dragView and its desired destination? Not quite, as
+ this doesn't necessarily coincide with the interaction of the dragView and items occupying
+ those cells. Instead we use some heuristics to often lock the vector to up, down, left
+ or right, which helps make pushing feel right.
+ */
+ private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
+ int spanY, View dragView, int[] resultDirection) {
+ int[] targetDestination = new int[2];
+
+ findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
+ Rect dragRect = new Rect();
+ regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
+ dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
+
+ Rect dropRegionRect = new Rect();
+ getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
+ dragView, dropRegionRect, mIntersectingViews);
+
+ int dropRegionSpanX = dropRegionRect.width();
+ int dropRegionSpanY = dropRegionRect.height();
+
+ regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
+ dropRegionRect.height(), dropRegionRect);
+
+ int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
+ int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
+
+ if (dropRegionSpanX == mCountX || spanX == mCountX) {
+ deltaX = 0;
+ }
+ if (dropRegionSpanY == mCountY || spanY == mCountY) {
+ deltaY = 0;
+ }
+
+ if (deltaX == 0 && deltaY == 0) {
+ // No idea what to do, give a random direction.
+ resultDirection[0] = 1;
+ resultDirection[1] = 0;
+ } else {
+ computeDirectionVector(deltaX, deltaY, resultDirection);
+ }
+ }
+
+ // For a given cell and span, fetch the set of views intersecting the region.
+ private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
+ View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
+ if (boundingRect != null) {
+ boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
+ }
+ intersectingViews.clear();
+ Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
+ Rect r1 = new Rect();
+ final int count = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ if (child == dragView) continue;
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
+ if (Rect.intersects(r0, r1)) {
+ mIntersectingViews.add(child);
+ if (boundingRect != null) {
+ boundingRect.union(r1);
+ }
+ }
+ }
+ }
+
+ boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
+ View dragView, int[] result) {
+ result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
+ getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
+ mIntersectingViews);
+ return !mIntersectingViews.isEmpty();
+ }
+
+ void revertTempState() {
+ if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
+ final int count = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
+ lp.tmpCellX = lp.cellX;
+ lp.tmpCellY = lp.cellY;
+ animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
+ 0, false, false);
+ }
+ }
+ completeAndClearReorderHintAnimations();
+ setItemPlacementDirty(false);
+ }
+
+ int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
+ View dragView, int[] result, int resultSpan[], int mode) {
+ // First we determine if things have moved enough to cause a different layout
+ result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
+
+ if (resultSpan == null) {
+ resultSpan = new int[2];
+ }
+
+ // When we are checking drop validity or actually dropping, we don't recompute the
+ // direction vector, since we want the solution to match the preview, and it's possible
+ // that the exact position of the item has changed to result in a new reordering outcome.
+ if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
+ && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
+ mDirectionVector[0] = mPreviousReorderDirection[0];
+ mDirectionVector[1] = mPreviousReorderDirection[1];
+ // We reset this vector after drop
+ if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
+ mPreviousReorderDirection[0] = INVALID_DIRECTION;
+ mPreviousReorderDirection[1] = INVALID_DIRECTION;
+ }
+
+ } else {
+ getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
+ mPreviousReorderDirection[0] = mDirectionVector[0];
+ mPreviousReorderDirection[1] = mDirectionVector[1];
+ }
+
+ ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
+ spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
+
+ // We attempt the approach which doesn't shuffle views at all
+ ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
+ minSpanY, spanX, spanY, dragView, new ItemConfiguration());
+
+ ItemConfiguration finalSolution = null;
+ if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
+ finalSolution = swapSolution;
+ } else if (noShuffleSolution.isSolution) {
+ finalSolution = noShuffleSolution;
+ }
+
+ boolean foundSolution = true;
+ if (!DESTRUCTIVE_REORDER) {
+ setUseTempCoords(true);
+ }
+
+ if (finalSolution != null) {
+ result[0] = finalSolution.dragViewX;
+ result[1] = finalSolution.dragViewY;
+ resultSpan[0] = finalSolution.dragViewSpanX;
+ resultSpan[1] = finalSolution.dragViewSpanY;
+
+ // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
+ // committing anything or animating anything as we just want to determine if a solution
+ // exists
+ if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
+ if (!DESTRUCTIVE_REORDER) {
+ copySolutionToTempState(finalSolution, dragView);
+ }
+ setItemPlacementDirty(true);
+ animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
+
+ if (!DESTRUCTIVE_REORDER &&
+ (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
+ commitTempPlacement();
+ completeAndClearReorderHintAnimations();
+ setItemPlacementDirty(false);
+ } else {
+ beginOrAdjustHintAnimations(finalSolution, dragView,
+ REORDER_ANIMATION_DURATION);
+ }
+ }
+ } else {
+ foundSolution = false;
+ result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
+ }
+
+ if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
+ setUseTempCoords(false);
+ }
+
+ mShortcutsAndWidgets.requestLayout();
+ return result;
+ }
+
+ void setItemPlacementDirty(boolean dirty) {
+ mItemPlacementDirty = dirty;
+ }
+ boolean isItemPlacementDirty() {
+ return mItemPlacementDirty;
+ }
+
+ private class ItemConfiguration {
+ HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
+ boolean isSolution = false;
+ int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
+
+ int area() {
+ return dragViewSpanX * dragViewSpanY;
+ }
+ }
+
+ private class CellAndSpan {
+ int x, y;
+ int spanX, spanY;
+
+ public CellAndSpan(int x, int y, int spanX, int spanY) {
+ this.x = x;
+ this.y = y;
+ this.spanX = spanX;
+ this.spanY = spanY;
+ }
+ }
+
/**
* Find a vacant area that will fit the given bounds nearest the requested
* cell location. Uses Euclidean distance to score multiple vacant areas.
@@ -1237,6 +2346,27 @@ public class CellLayout extends ViewGroup {
}
/**
+ * Find a vacant area that will fit the given bounds nearest the requested
+ * cell location. Uses Euclidean distance to score multiple vacant areas.
+ *
+ * @param pixelX The X location at which you want to search for a vacant area.
+ * @param pixelY The Y location at which you want to search for a vacant area.
+ * @param minSpanX The minimum horizontal span required
+ * @param minSpanY The minimum vertical span required
+ * @param spanX Horizontal span of the object.
+ * @param spanY Vertical span of the object.
+ * @param ignoreView Considers space occupied by this view as unoccupied
+ * @param result Previously returned value to possibly recycle.
+ * @return The X, Y cell of a vacant area that can contain this object,
+ * nearest the requested location.
+ */
+ int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
+ int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
+ return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
+ result, resultSpan, mOccupied);
+ }
+
+ /**
* Find a starting cell position that will fit the given bounds nearest the requested
* cell location. Uses Euclidean distance to score multiple vacant areas.
*
@@ -1272,7 +2402,7 @@ public class CellLayout extends ViewGroup {
* @return True if a vacant cell of the specified dimension was found, false otherwise.
*/
boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
- return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null);
+ return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
}
/**
@@ -1286,7 +2416,8 @@ public class CellLayout extends ViewGroup {
* @return
*/
boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
- return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, ignoreView);
+ return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
+ ignoreView, mOccupied);
}
/**
@@ -1304,16 +2435,16 @@ public class CellLayout extends ViewGroup {
boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
int intersectX, int intersectY) {
return findCellForSpanThatIntersectsIgnoring(
- cellXY, spanX, spanY, intersectX, intersectY, null);
+ cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
}
/**
* The superset of the above two methods
*/
boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
- int intersectX, int intersectY, View ignoreView) {
+ int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
// mark space take by ignoreView as available (method checks if ignoreView is null)
- markCellsAsUnoccupiedForView(ignoreView);
+ markCellsAsUnoccupiedForView(ignoreView, occupied);
boolean foundCell = false;
while (true) {
@@ -1339,7 +2470,7 @@ public class CellLayout extends ViewGroup {
for (int x = startX; x < endX; x++) {
for (int i = 0; i < spanX; i++) {
for (int j = 0; j < spanY; j++) {
- if (mOccupied[x + i][y + j]) {
+ if (occupied[x + i][y + j]) {
// small optimization: we can skip to after the column we just found
// an occupied cell
x += i;
@@ -1367,7 +2498,7 @@ public class CellLayout extends ViewGroup {
}
// re-mark space taken by ignoreView as occupied
- markCellsAsOccupiedForView(ignoreView);
+ markCellsAsOccupiedForView(ignoreView, occupied);
return foundCell;
}
@@ -1403,11 +2534,10 @@ public class CellLayout extends ViewGroup {
}
// Invalidate the drag data
- mDragCell[0] = -1;
- mDragCell[1] = -1;
+ mDragCell[0] = mDragCell[1] = -1;
mDragOutlineAnims[mDragOutlineCurrent].animateOut();
mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
-
+ revertTempState();
setIsDragOverlapping(false);
}
@@ -1435,7 +2565,7 @@ public class CellLayout extends ViewGroup {
* @param cellVSpan Height in cells
* @param resultRect Rect into which to put the results
*/
- public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) {
+ public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
final int cellWidth = mCellWidth;
final int cellHeight = mCellHeight;
final int widthGap = mWidthGap;
@@ -1610,28 +2740,35 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
}
}
- public void onMove(View view, int newCellX, int newCellY) {
- LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
markCellsAsUnoccupiedForView(view);
- markCellsForView(newCellX, newCellY, lp.cellHSpan, lp.cellVSpan, true);
+ markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
}
public void markCellsAsOccupiedForView(View view) {
- if (view == null || view.getParent() != mChildren) return;
+ markCellsAsOccupiedForView(view, mOccupied);
+ }
+ public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
+ if (view == null || view.getParent() != mShortcutsAndWidgets) return;
LayoutParams lp = (LayoutParams) view.getLayoutParams();
- markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
+ markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
}
public void markCellsAsUnoccupiedForView(View view) {
- if (view == null || view.getParent() != mChildren) return;
+ markCellsAsUnoccupiedForView(view, mOccupied);
+ }
+ public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
+ if (view == null || view.getParent() != mShortcutsAndWidgets) return;
LayoutParams lp = (LayoutParams) view.getLayoutParams();
- markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
+ markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
}
- private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean value) {
+ private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
+ boolean value) {
+ if (cellX < 0 || cellY < 0) return;
for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
- mOccupied[x][y] = value;
+ occupied[x][y] = value;
}
}
}
@@ -1694,6 +2831,21 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
public int cellY;
/**
+ * Temporary horizontal location of the item in the grid during reorder
+ */
+ public int tmpCellX;
+
+ /**
+ * Temporary vertical location of the item in the grid during reorder
+ */
+ public int tmpCellY;
+
+ /**
+ * Indicates that the temporary coordinates should be used to layout the items
+ */
+ public boolean useTmpCoords;
+
+ /**
* Number of cells spanned horizontally by the item.
*/
@ViewDebug.ExportedProperty
@@ -1711,6 +2863,12 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
*/
public boolean isLockedToGrid = true;
+ /**
+ * Indicates whether this item can be reordered. Always true except in the case of the
+ * the AllApps button.
+ */
+ public boolean canReorder = true;
+
// X coordinate of the view in the layout.
@ViewDebug.ExportedProperty
int x;
@@ -1752,15 +2910,15 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
if (isLockedToGrid) {
final int myCellHSpan = cellHSpan;
final int myCellVSpan = cellVSpan;
- final int myCellX = cellX;
- final int myCellY = cellY;
+ final int myCellX = useTmpCoords ? tmpCellX : cellX;
+ final int myCellY = useTmpCoords ? tmpCellY : cellY;
width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
leftMargin - rightMargin;
height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
topMargin - bottomMargin;
- x = myCellX * (cellWidth + widthGap) + leftMargin;
- y = myCellY * (cellHeight + heightGap) + topMargin;
+ x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
+ y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
}
}
diff --git a/src/com/android/launcher2/CheckLongPressHelper.java b/src/com/android/launcher2/CheckLongPressHelper.java
new file mode 100644
index 000000000..5c3752ad6
--- /dev/null
+++ b/src/com/android/launcher2/CheckLongPressHelper.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2012 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.view.View;
+
+public class CheckLongPressHelper {
+ private View mView;
+ private boolean mHasPerformedLongPress;
+ private CheckForLongPress mPendingCheckForLongPress;
+
+ class CheckForLongPress implements Runnable {
+ public void run() {
+ if ((mView.getParent() != null) && mView.hasWindowFocus()
+ && !mHasPerformedLongPress) {
+ if (mView.performLongClick()) {
+ mView.setPressed(false);
+ mHasPerformedLongPress = true;
+ }
+ }
+ }
+ }
+
+ public CheckLongPressHelper(View v) {
+ mView = v;
+ }
+
+ public void postCheckForLongPress() {
+ mHasPerformedLongPress = false;
+
+ if (mPendingCheckForLongPress == null) {
+ mPendingCheckForLongPress = new CheckForLongPress();
+ }
+ mView.postDelayed(mPendingCheckForLongPress, LauncherApplication.getLongPressTimeout());
+ }
+
+ public void cancelLongPress() {
+ mHasPerformedLongPress = false;
+ if (mPendingCheckForLongPress != null) {
+ mView.removeCallbacks(mPendingCheckForLongPress);
+ mPendingCheckForLongPress = null;
+ }
+ }
+
+ public boolean hasPerformedLongPress() {
+ return mHasPerformedLongPress;
+ }
+}
diff --git a/src/com/android/launcher2/DeleteDropTarget.java b/src/com/android/launcher2/DeleteDropTarget.java
index f19970823..7e4225be4 100644
--- a/src/com/android/launcher2/DeleteDropTarget.java
+++ b/src/com/android/launcher2/DeleteDropTarget.java
@@ -16,26 +16,34 @@
package com.android.launcher2;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.TransitionDrawable;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
import com.android.launcher.R;
public class DeleteDropTarget extends ButtonDropTarget {
+ private static int DELETE_ANIMATION_DURATION = 285;
+ private static int MODE_FLING_DELETE_TO_TRASH = 0;
+ private static int MODE_FLING_DELETE_ALONG_VECTOR = 1;
+
+ private final int mFlingDeleteMode = MODE_FLING_DELETE_ALONG_VECTOR;
- private static int DELETE_ANIMATION_DURATION = 250;
private ColorStateList mOriginalTextColor;
- private int mHoverColor = 0xFFFF0000;
private TransitionDrawable mUninstallDrawable;
private TransitionDrawable mRemoveDrawable;
private TransitionDrawable mCurrentDrawable;
@@ -58,8 +66,6 @@ public class DeleteDropTarget extends ButtonDropTarget {
// Get the hover color
Resources r = getResources();
mHoverColor = r.getColor(R.color.delete_target_hover_tint);
- mHoverPaint.setColorFilter(new PorterDuffColorFilter(
- mHoverColor, PorterDuff.Mode.SRC_ATOP));
mUninstallDrawable = (TransitionDrawable)
r.getDrawable(R.drawable.uninstall_target_selector);
mRemoveDrawable = (TransitionDrawable) r.getDrawable(R.drawable.remove_target_selector);
@@ -99,6 +105,15 @@ public class DeleteDropTarget extends ButtonDropTarget {
return (d.dragSource instanceof Workspace) && (d.dragInfo instanceof FolderInfo);
}
+ private void setHoverColor() {
+ mCurrentDrawable.startTransition(mTransitionDuration);
+ setTextColor(mHoverColor);
+ }
+ private void resetHoverColor() {
+ mCurrentDrawable.resetTransition();
+ setTextColor(mOriginalTextColor);
+ }
+
@Override
public boolean acceptDrop(DragObject d) {
// We can remove everything including App shortcuts, folders, widgets, etc.
@@ -134,8 +149,7 @@ public class DeleteDropTarget extends ButtonDropTarget {
mCurrentDrawable = (TransitionDrawable) getCompoundDrawables()[0];
mActive = isVisible;
- mCurrentDrawable.resetTransition();
- setTextColor(mOriginalTextColor);
+ resetHoverColor();
((ViewGroup) getParent()).setVisibility(isVisible ? View.VISIBLE : View.GONE);
if (getText().length() > 0) {
setText(isUninstall ? R.string.delete_target_uninstall_label
@@ -152,35 +166,27 @@ public class DeleteDropTarget extends ButtonDropTarget {
public void onDragEnter(DragObject d) {
super.onDragEnter(d);
- mCurrentDrawable.startTransition(mTransitionDuration);
- setTextColor(mHoverColor);
+ setHoverColor();
}
public void onDragExit(DragObject d) {
super.onDragExit(d);
if (!d.dragComplete) {
- mCurrentDrawable.resetTransition();
- setTextColor(mOriginalTextColor);
+ resetHoverColor();
+ } else {
+ // Restore the hover color if we are deleting
+ d.dragView.setColor(mHoverColor);
}
}
private void animateToTrashAndCompleteDrop(final DragObject d) {
DragLayer dragLayer = mLauncher.getDragLayer();
Rect from = new Rect();
- Rect to = new Rect();
dragLayer.getViewRectRelativeToSelf(d.dragView, from);
- dragLayer.getViewRectRelativeToSelf(this, to);
-
- int width = mCurrentDrawable.getIntrinsicWidth();
- int height = mCurrentDrawable.getIntrinsicHeight();
- to.set(to.left + getPaddingLeft(), to.top + getPaddingTop(),
- to.left + getPaddingLeft() + width, to.bottom);
-
- // Center the destination rect about the trash icon
- int xOffset = (int) -(d.dragView.getMeasuredWidth() - width) / 2;
- int yOffset = (int) -(d.dragView.getMeasuredHeight() - height) / 2;
- to.offset(xOffset, yOffset);
+ Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
+ mCurrentDrawable.getIntrinsicWidth(), mCurrentDrawable.getIntrinsicHeight());
+ float scale = (float) to.width() / from.width();
mSearchDropTargetBar.deferOnDragEnd();
Runnable onAnimationEndRunnable = new Runnable() {
@@ -191,9 +197,10 @@ public class DeleteDropTarget extends ButtonDropTarget {
completeDrop(d);
}
};
- dragLayer.animateView(d.dragView, from, to, 0.1f, 0.1f,
+ dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
DELETE_ANIMATION_DURATION, new DecelerateInterpolator(2),
- new DecelerateInterpolator(1.5f), onAnimationEndRunnable, false);
+ new LinearInterpolator(), onAnimationEndRunnable,
+ DragLayer.ANIMATION_END_DISAPPEAR, null);
}
private void completeDrop(DragObject d) {
@@ -231,4 +238,187 @@ public class DeleteDropTarget extends ButtonDropTarget {
public void onDrop(DragObject d) {
animateToTrashAndCompleteDrop(d);
}
+
+ /**
+ * Creates an animation from the current drag view to the delete trash icon.
+ */
+ private AnimatorUpdateListener createFlingToTrashAnimatorListener(final DragLayer dragLayer,
+ DragObject d, PointF vel, ViewConfiguration config) {
+ final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
+ mCurrentDrawable.getIntrinsicWidth(), mCurrentDrawable.getIntrinsicHeight());
+ final Rect from = new Rect();
+ dragLayer.getViewRectRelativeToSelf(d.dragView, from);
+
+ // Calculate how far along the velocity vector we should put the intermediate point on
+ // the bezier curve
+ float velocity = Math.abs(vel.length());
+ float vp = Math.min(1f, velocity / (config.getScaledMaximumFlingVelocity() / 2f));
+ int offsetY = (int) (-from.top * vp);
+ int offsetX = (int) (offsetY / (vel.y / vel.x));
+ final float y2 = from.top + offsetY; // intermediate t/l
+ final float x2 = from.left + offsetX;
+ final float x1 = from.left; // drag view t/l
+ final float y1 = from.top;
+ final float x3 = to.left; // delete target t/l
+ final float y3 = to.top;
+
+ final TimeInterpolator scaleAlphaInterpolator = new TimeInterpolator() {
+ @Override
+ public float getInterpolation(float t) {
+ return t * t * t * t * t * t * t * t;
+ }
+ };
+ return new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final DragView dragView = (DragView) dragLayer.getAnimatedView();
+ float t = ((Float) animation.getAnimatedValue()).floatValue();
+ float tp = scaleAlphaInterpolator.getInterpolation(t);
+ float initialScale = dragView.getInitialScale();
+ float finalAlpha = 0.5f;
+ float scale = dragView.getScaleX();
+ float x1o = ((1f - scale) * dragView.getMeasuredWidth()) / 2f;
+ float y1o = ((1f - scale) * dragView.getMeasuredHeight()) / 2f;
+ float x = (1f - t) * (1f - t) * (x1 - x1o) + 2 * (1f - t) * t * (x2 - x1o) +
+ (t * t) * x3;
+ float y = (1f - t) * (1f - t) * (y1 - y1o) + 2 * (1f - t) * t * (y2 - x1o) +
+ (t * t) * y3;
+
+ dragView.setTranslationX(x);
+ dragView.setTranslationY(y);
+ dragView.setScaleX(initialScale * (1f - tp));
+ dragView.setScaleY(initialScale * (1f - tp));
+ dragView.setAlpha(finalAlpha + (1f - finalAlpha) * (1f - tp));
+ }
+ };
+ }
+
+ /**
+ * Creates an animation from the current drag view along its current velocity vector.
+ * For this animation, the alpha runs for a fixed duration and we update the position
+ * progressively.
+ */
+ private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
+ private static float FRICTION = 0.93f;
+
+ private DragLayer mDragLayer;
+ private PointF mVelocity;
+ private Rect mFrom;
+ private long mPrevTime;
+ private boolean mHasOffsetForScale;
+
+ private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(1.5f);
+
+ public FlingAlongVectorAnimatorUpdateListener(DragLayer dragLayer, PointF vel, Rect from,
+ long startTime) {
+ mDragLayer = dragLayer;
+ mVelocity = vel;
+ mFrom = from;
+ mPrevTime = startTime;
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final DragView dragView = (DragView) mDragLayer.getAnimatedView();
+ float t = ((Float) animation.getAnimatedValue()).floatValue();
+ long curTime = AnimationUtils.currentAnimationTimeMillis();
+
+ if (!mHasOffsetForScale) {
+ mHasOffsetForScale = true;
+ float scale = dragView.getScaleX();
+ float xOffset = ((scale - 1f) * dragView.getMeasuredWidth()) / 2f;
+ float yOffset = ((scale - 1f) * dragView.getMeasuredHeight()) / 2f;
+
+ mFrom.left += xOffset;
+ mFrom.top += yOffset;
+ }
+
+ mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
+ mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
+
+ dragView.setTranslationX(mFrom.left);
+ dragView.setTranslationY(mFrom.top);
+ dragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
+
+ mVelocity.x *= FRICTION;
+ mVelocity.y *= FRICTION;
+ mPrevTime = curTime;
+ }
+ };
+ private AnimatorUpdateListener createFlingAlongVectorAnimatorListener(final DragLayer dragLayer,
+ DragObject d, PointF vel, final long startTime, final int duration,
+ ViewConfiguration config) {
+ final Rect from = new Rect();
+ dragLayer.getViewRectRelativeToSelf(d.dragView, from);
+
+ return new FlingAlongVectorAnimatorUpdateListener(dragLayer, vel, from, startTime);
+ }
+
+ public void onFlingToDelete(final DragObject d, int x, int y, PointF vel) {
+ final boolean isAllApps = d.dragSource instanceof AppsCustomizePagedView;
+
+ // Don't highlight the icon as it's animating
+ d.dragView.setColor(0);
+ d.dragView.updateInitialScaleToCurrentScale();
+ // Don't highlight the target if we are flinging from AllApps
+ if (isAllApps) {
+ resetHoverColor();
+ }
+
+ if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
+ // Defer animating out the drop target if we are animating to it
+ mSearchDropTargetBar.deferOnDragEnd();
+ mSearchDropTargetBar.finishAnimations();
+ }
+
+ final ViewConfiguration config = ViewConfiguration.get(mLauncher);
+ final DragLayer dragLayer = mLauncher.getDragLayer();
+ final int duration = DELETE_ANIMATION_DURATION;
+ final long startTime = AnimationUtils.currentAnimationTimeMillis();
+
+ // NOTE: Because it takes time for the first frame of animation to actually be
+ // called and we expect the animation to be a continuation of the fling, we have
+ // to account for the time that has elapsed since the fling finished. And since
+ // we don't have a startDelay, we will always get call to update when we call
+ // start() (which we want to ignore).
+ final TimeInterpolator tInterpolator = new TimeInterpolator() {
+ private int mCount = -1;
+ private float mOffset = 0f;
+
+ @Override
+ public float getInterpolation(float t) {
+ if (mCount < 0) {
+ mCount++;
+ } else if (mCount == 0) {
+ mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
+ startTime) / duration);
+ mCount++;
+ }
+ return Math.min(1f, mOffset + t);
+ }
+ };
+ AnimatorUpdateListener updateCb = null;
+ if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
+ updateCb = createFlingToTrashAnimatorListener(dragLayer, d, vel, config);
+ } else if (mFlingDeleteMode == MODE_FLING_DELETE_ALONG_VECTOR) {
+ updateCb = createFlingAlongVectorAnimatorListener(dragLayer, d, vel, startTime,
+ duration, config);
+ }
+ Runnable onAnimationEndRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mSearchDropTargetBar.onDragEnd();
+
+ // If we are dragging from AllApps, then we allow AppsCustomizePagedView to clean up
+ // itself, otherwise, complete the drop to initiate the deletion process
+ if (!isAllApps) {
+ mLauncher.exitSpringLoadedDragMode();
+ completeDrop(d);
+ }
+ mLauncher.getDragController().onDeferredEndFling(d);
+ }
+ };
+ dragLayer.animateView(d.dragView, updateCb, duration, tInterpolator, onAnimationEndRunnable,
+ DragLayer.ANIMATION_END_DISAPPEAR, null);
+ }
}
diff --git a/src/com/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java
index a120ac569..2a1d65adc 100644
--- a/src/com/android/launcher2/DragController.java
+++ b/src/com/android/launcher2/DragController.java
@@ -19,6 +19,7 @@ package com.android.launcher2;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Handler;
import android.os.IBinder;
@@ -26,6 +27,7 @@ import android.os.Vibrator;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.inputmethod.InputMethodManager;
@@ -47,8 +49,9 @@ public class DragController {
/** Indicates the drag is a copy. */
public static int DRAG_ACTION_COPY = 1;
- private static final int SCROLL_DELAY = 600;
- private static final int VIBRATE_DURATION = 35;
+ private static final int SCROLL_DELAY = 500;
+ private static final int RESCROLL_DELAY = 750;
+ private static final int VIBRATE_DURATION = 15;
private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
@@ -59,6 +62,9 @@ public class DragController {
static final int SCROLL_LEFT = 0;
static final int SCROLL_RIGHT = 1;
+ private static final float MAX_FLING_DEGREES = 35f;
+ private static final int FLING_TO_DELETE_THRESHOLD_Y_VELOCITY = -1400;
+
private Launcher mLauncher;
private Handler mHandler;
private final Vibrator mVibrator = new Vibrator();
@@ -85,8 +91,8 @@ public class DragController {
/** Who can receive drop events */
private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>();
-
private ArrayList<DragListener> mListeners = new ArrayList<DragListener>();
+ private DropTarget mFlingToDeleteDropTarget;
/** The window token used as the parent for the DragView. */
private IBinder mWindowToken;
@@ -110,6 +116,9 @@ public class DragController {
private int mTmpPoint[] = new int[2];
private Rect mDragLayerRect = new Rect();
+ protected int mFlingToDeleteThresholdVelocity;
+ private VelocityTracker mVelocityTracker;
+
/**
* Interface to receive notifications when a drag starts or stops
*/
@@ -140,6 +149,10 @@ public class DragController {
mLauncher = launcher;
mHandler = new Handler();
mScrollZone = launcher.getResources().getDimensionPixelSize(R.dimen.scroll_zone);
+ mVelocityTracker = VelocityTracker.obtain();
+
+ float density = launcher.getResources().getDisplayMetrics().density;
+ mFlingToDeleteThresholdVelocity = (int) (FLING_TO_DELETE_THRESHOLD_Y_VELOCITY * density);
}
public boolean dragging() {
@@ -184,7 +197,7 @@ public class DragController {
int dragLayerX = loc[0];
int dragLayerY = loc[1];
- startDrag(b, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion);
+ startDrag(b, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion, 1f);
b.recycle();
if (dragAction == DRAG_ACTION_MOVE) {
@@ -205,13 +218,16 @@ public class DragController {
* Makes dragging feel more precise, e.g. you can clip out a transparent border
*/
public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, int dragAction,
- Rect dragRegion) {
+ Rect dragRegion, float initialDragViewScale) {
int[] loc = mCoordinatesTemp;
mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
- int dragLayerX = loc[0];
- int dragLayerY = loc[1];
+ int dragLayerX = loc[0] + v.getPaddingLeft() +
+ (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2);
+ int dragLayerY = loc[1] + v.getPaddingTop() +
+ (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
- startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion);
+ startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion,
+ initialDragViewScale);
if (dragAction == DRAG_ACTION_MOVE) {
v.setVisibility(View.GONE);
@@ -229,28 +245,12 @@ public class DragController {
* @param dragInfo The data associated with the object that is being dragged
* @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
* {@link #DRAG_ACTION_COPY}
- */
- public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
- DragSource source, Object dragInfo, int dragAction) {
- startDrag(b, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, null);
- }
-
- /**
- * Starts a drag.
- *
- * @param b The bitmap to display as the drag image. It will be re-scaled to the
- * enlarged size.
- * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
- * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
- * @param source An object representing where the drag originated
- * @param dragInfo The data associated with the object that is being dragged
- * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
- * {@link #DRAG_ACTION_COPY}
* @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
* Makes dragging feel more precise, e.g. you can clip out a transparent border
*/
public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
- DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion) {
+ DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
+ float initialDragViewScale) {
if (PROFILE_DRAWING_DURING_DRAG) {
android.os.Debug.startMethodTracing("Launcher");
}
@@ -285,7 +285,7 @@ public class DragController {
mVibrator.vibrate(VIBRATE_DURATION);
final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
- registrationY, 0, 0, b.getWidth(), b.getHeight());
+ registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
if (dragOffset != null) {
dragView.setDragVisualizeOffset(new Point(dragOffset));
@@ -363,9 +363,10 @@ public class DragController {
if (mLastDropTarget != null) {
mLastDropTarget.onDragExit(mDragObject);
}
+ mDragObject.deferDragViewCleanupPostAnimation = false;
mDragObject.cancelled = true;
mDragObject.dragComplete = true;
- mDragObject.dragSource.onDropCompleted(null, mDragObject, false);
+ mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
}
endDrag();
}
@@ -376,7 +377,11 @@ public class DragController {
if (rawDragInfo instanceof ShortcutInfo) {
ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo;
for (ApplicationInfo info : apps) {
- if (dragInfo.intent.getComponent().equals(info.intent.getComponent())) {
+ // Added null checks to prevent NPE we've seen in the wild
+ if (dragInfo != null &&
+ dragInfo.intent != null &&
+ info.intent != null &&
+ dragInfo.intent.getComponent().equals(info.intent.getComponent())) {
cancelDrag();
return;
}
@@ -388,16 +393,43 @@ public class DragController {
private void endDrag() {
if (mDragging) {
mDragging = false;
- for (DragListener listener : mListeners) {
- listener.onDragEnd();
- }
+ clearScrollRunnable();
+ boolean isDeferred = false;
if (mDragObject.dragView != null) {
- mDragObject.dragView.remove();
+ isDeferred = mDragObject.deferDragViewCleanupPostAnimation;
+ if (!isDeferred) {
+ mDragObject.dragView.remove();
+ }
mDragObject.dragView = null;
}
+
+ // Only end the drag if we are not deferred
+ if (!isDeferred) {
+ for (DragListener listener : mListeners) {
+ listener.onDragEnd();
+ }
+ }
+ }
+
+ releaseVelocityTracker();
+ }
+
+ /**
+ * This only gets called as a result of drag view cleanup being deferred in endDrag();
+ */
+ void onDeferredEndDrag(DragView dragView) {
+ dragView.remove();
+
+ // If we skipped calling onDragEnd() before, do it now
+ for (DragListener listener : mListeners) {
+ listener.onDragEnd();
}
}
+ void onDeferredEndFling(DropTarget.DragObject d) {
+ d.dragSource.onFlingToDeleteCompleted();
+ }
+
/**
* Clamps the position to the drag layer bounds.
*/
@@ -416,8 +448,11 @@ public class DragController {
Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging="
+ mDragging);
}
- final int action = ev.getAction();
+ // Update the velocity tracker
+ acquireVelocityTrackerAndAddMovement(ev);
+
+ final int action = ev.getAction();
final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
final int dragLayerX = dragLayerPos[0];
final int dragLayerY = dragLayerPos[1];
@@ -433,7 +468,12 @@ public class DragController {
break;
case MotionEvent.ACTION_UP:
if (mDragging) {
- drop(dragLayerX, dragLayerY);
+ PointF vec = isFlingingToDelete(mDragObject.dragSource);
+ if (vec != null) {
+ dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
+ } else {
+ drop(dragLayerX, dragLayerY);
+ }
}
endDrag();
break;
@@ -456,6 +496,15 @@ public class DragController {
return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
}
+ private void clearScrollRunnable() {
+ mHandler.removeCallbacks(mScrollRunnable);
+ if (mScrollState == SCROLL_WAITING_IN_ZONE) {
+ mScrollState = SCROLL_OUTSIDE_ZONE;
+ mScrollRunnable.setDirection(SCROLL_RIGHT);
+ mDragScroller.onExitScrollArea();
+ }
+ }
+
private void handleMoveEvent(int x, int y) {
mDragObject.dragView.move(x, y);
@@ -491,30 +540,32 @@ public class DragController {
Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
mLastTouch[0] = x;
mLastTouch[1] = y;
+ final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY;
if (x < mScrollZone) {
- if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
+ if (mScrollState == SCROLL_OUTSIDE_ZONE) {
mScrollState = SCROLL_WAITING_IN_ZONE;
if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) {
mScrollRunnable.setDirection(SCROLL_LEFT);
- mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
+ mHandler.postDelayed(mScrollRunnable, delay);
}
}
} else if (x > mScrollView.getWidth() - mScrollZone) {
- if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
+ if (mScrollState == SCROLL_OUTSIDE_ZONE) {
mScrollState = SCROLL_WAITING_IN_ZONE;
if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) {
mScrollRunnable.setDirection(SCROLL_RIGHT);
- mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
+ mHandler.postDelayed(mScrollRunnable, delay);
}
}
} else {
- if (mScrollState == SCROLL_WAITING_IN_ZONE) {
- mScrollState = SCROLL_OUTSIDE_ZONE;
- mScrollRunnable.setDirection(SCROLL_RIGHT);
- mHandler.removeCallbacks(mScrollRunnable);
- mDragScroller.onExitScrollArea();
- }
+ clearScrollRunnable();
+ }
+ }
+
+ public void forceMoveEvent() {
+ if (mDragging) {
+ handleMoveEvent(mDragObject.x, mDragObject.y);
}
}
@@ -526,6 +577,9 @@ public class DragController {
return false;
}
+ // Update the velocity tracker
+ acquireVelocityTrackerAndAddMovement(ev);
+
final int action = ev.getAction();
final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
final int dragLayerX = dragLayerPos[0];
@@ -550,14 +604,20 @@ public class DragController {
case MotionEvent.ACTION_UP:
// Ensure that we've processed a move event at the current pointer location.
handleMoveEvent(dragLayerX, dragLayerY);
-
mHandler.removeCallbacks(mScrollRunnable);
+
if (mDragging) {
- drop(dragLayerX, dragLayerY);
+ PointF vec = isFlingingToDelete(mDragObject.dragSource);
+ if (vec != null) {
+ dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
+ } else {
+ drop(dragLayerX, dragLayerY);
+ }
}
endDrag();
break;
case MotionEvent.ACTION_CANCEL:
+ mHandler.removeCallbacks(mScrollRunnable);
cancelDrag();
break;
}
@@ -565,6 +625,60 @@ public class DragController {
return true;
}
+ /**
+ * Determines whether the user flung the current item to delete it.
+ *
+ * @return the vector at which the item was flung, or null if no fling was detected.
+ */
+ private PointF isFlingingToDelete(DragSource source) {
+ if (mFlingToDeleteDropTarget == null) return null;
+ if (!source.supportsFlingToDelete()) return null;
+
+ ViewConfiguration config = ViewConfiguration.get(mLauncher);
+ mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
+
+ if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
+ // Do a quick dot product test to ensure that we are flinging upwards
+ PointF vel = new PointF(mVelocityTracker.getXVelocity(),
+ mVelocityTracker.getYVelocity());
+ PointF upVec = new PointF(0f, -1f);
+ float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
+ (vel.length() * upVec.length()));
+ if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
+ return vel;
+ }
+ }
+ return null;
+ }
+
+ private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) {
+ final int[] coordinates = mCoordinatesTemp;
+
+ mDragObject.x = coordinates[0];
+ mDragObject.y = coordinates[1];
+
+ // Clean up dragging on the target if it's not the current fling delete target otherwise,
+ // start dragging to it.
+ if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) {
+ mLastDropTarget.onDragExit(mDragObject);
+ }
+
+ // Drop onto the fling-to-delete target
+ boolean accepted = false;
+ mFlingToDeleteDropTarget.onDragEnter(mDragObject);
+ // We must set dragComplete to true _only_ after we "enter" the fling-to-delete target for
+ // "drop"
+ mDragObject.dragComplete = true;
+ mFlingToDeleteDropTarget.onDragExit(mDragObject);
+ if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) {
+ mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, mDragObject.x, mDragObject.y,
+ vel);
+ accepted = true;
+ }
+ mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true,
+ accepted);
+ }
+
private void drop(float x, float y) {
final int[] coordinates = mCoordinatesTemp;
final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
@@ -580,7 +694,7 @@ public class DragController {
accepted = true;
}
}
- mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, accepted);
+ mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted);
}
private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
@@ -655,6 +769,27 @@ public class DragController {
}
/**
+ * Sets the current fling-to-delete drop target.
+ */
+ public void setFlingToDeleteDropTarget(DropTarget target) {
+ mFlingToDeleteDropTarget = target;
+ }
+
+ private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+ }
+
+ private void releaseVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ /**
* Set which view scrolls for touch events near the edge of the screen.
*/
public void setScrollView(View v) {
@@ -681,6 +816,11 @@ public class DragController {
mScrollState = SCROLL_OUTSIDE_ZONE;
mDistanceSinceScroll = 0;
mDragScroller.onExitScrollArea();
+
+ if (isDragging()) {
+ // Force an update so that we can requeue the scroller if necessary
+ forceMoveEvent();
+ }
}
}
diff --git a/src/com/android/launcher2/DragLayer.java b/src/com/android/launcher2/DragLayer.java
index 915477119..94528574d 100644
--- a/src/com/android/launcher2/DragLayer.java
+++ b/src/com/android/launcher2/DragLayer.java
@@ -18,15 +18,15 @@ package com.android.launcher2;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -34,6 +34,7 @@ import android.view.View;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
@@ -62,15 +63,17 @@ public class DragLayer extends FrameLayout {
private ValueAnimator mDropAnim = null;
private ValueAnimator mFadeOutAnim = null;
private TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
- private View mDropView = null;
+ private DragView mDropView = null;
+ private int mAnchorViewInitialScrollX = 0;
+ private View mAnchorView = null;
- private int[] mDropViewPos = new int[2];
- private float mDropViewScale;
- private float mDropViewAlpha;
private boolean mHoverPointClosesFolder = false;
private Rect mHitRect = new Rect();
private int mWorkspaceIndex = -1;
private int mQsbIndex = -1;
+ public static final int ANIMATION_END_DISAPPEAR = 0;
+ public static final int ANIMATION_END_FADE_OUT = 1;
+ public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
/**
* Used to create a new DragLayer from XML.
@@ -412,26 +415,29 @@ public class DragLayer extends FrameLayout {
animateViewIntoPosition(dragView, child, null);
}
- public void animateViewIntoPosition(DragView dragView, final int[] pos, float scale,
- Runnable onFinishRunnable) {
+ public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
+ float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
+ int duration) {
Rect r = new Rect();
getViewRectRelativeToSelf(dragView, r);
final int fromX = r.left;
final int fromY = r.top;
- animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], scale,
- onFinishRunnable, true, -1);
+ animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
+ onFinishRunnable, animationEndStyle, duration, null);
}
public void animateViewIntoPosition(DragView dragView, final View child,
final Runnable onFinishAnimationRunnable) {
- animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable);
+ animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, null);
}
public void animateViewIntoPosition(DragView dragView, final View child, int duration,
- final Runnable onFinishAnimationRunnable) {
- ((CellLayoutChildren) child.getParent()).measureChild(child);
+ final Runnable onFinishAnimationRunnable, View anchorView) {
+ ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
+ CellLayout parent = (CellLayout) (CellLayout) parentChildren.getParent();
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+ parentChildren.measureChild(child);
Rect r = new Rect();
getViewRectRelativeToSelf(dragView, r);
@@ -439,23 +445,28 @@ public class DragLayer extends FrameLayout {
int coord[] = new int[2];
coord[0] = lp.x;
coord[1] = lp.y;
+
// Since the child hasn't necessarily been laid out, we force the lp to be updated with
// the correct coordinates (above) and use these to determine the final location
float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
int toX = coord[0];
int toY = coord[1];
if (child instanceof TextView) {
+ float childrenScale = parent.getChildrenScale();
TextView tv = (TextView) child;
- Drawable d = tv.getCompoundDrawables()[1];
- // Center in the y coordinate about the target's drawable
- toY += Math.round(scale * tv.getPaddingTop());
- toY -= (dragView.getHeight() - (int) Math.round(scale * d.getIntrinsicHeight())) / 2;
- // Center in the x coordinate about the target's drawable
+ // The child may be scaled (always about the center of the view) so to account for it,
+ // we have to offset the position by the scaled size. Once we do that, we can center
+ // the drag view about the scaled child view.
+ toY += Math.round(((1f - childrenScale) * child.getMeasuredHeight()) / 2 +
+ scale * childrenScale * tv.getPaddingTop());
+ toY -= dragView.getMeasuredHeight() * (1 - scale * childrenScale) / 2;
toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
+
+ scale *= childrenScale;
} else if (child instanceof FolderIcon) {
// Account for holographic blur padding on the drag view
- toY -= HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS / 2;
+ toY -= Workspace.DRAG_BITMAP_PADDING / 2;
// Center in the x coordinate about the target's drawable
toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
} else {
@@ -467,34 +478,27 @@ public class DragLayer extends FrameLayout {
final int fromX = r.left;
final int fromY = r.top;
child.setVisibility(INVISIBLE);
- child.setAlpha(0);
Runnable onCompleteRunnable = new Runnable() {
public void run() {
child.setVisibility(VISIBLE);
- ObjectAnimator oa = ObjectAnimator.ofFloat(child, "alpha", 0f, 1f);
- oa.setDuration(60);
- oa.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(android.animation.Animator animation) {
- if (onFinishAnimationRunnable != null) {
- onFinishAnimationRunnable.run();
- }
- }
- });
- oa.start();
+ if (onFinishAnimationRunnable != null) {
+ onFinishAnimationRunnable.run();
+ }
}
};
- animateViewIntoPosition(dragView, fromX, fromY, toX, toY, scale,
- onCompleteRunnable, true, duration);
+ animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale,
+ onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
}
- private void animateViewIntoPosition(final View view, final int fromX, final int fromY,
- final int toX, final int toY, float finalScale, Runnable onCompleteRunnable,
- boolean fadeOut, int duration) {
+ public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
+ final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
+ float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
+ int animationEndStyle, int duration, View anchorView) {
Rect from = new Rect(fromX, fromY, fromX +
view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
- animateView(view, from, to, 1f, finalScale, duration, null, null, onCompleteRunnable, true);
+ animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
+ null, null, onCompleteRunnable, animationEndStyle, anchorView);
}
/**
@@ -514,11 +518,16 @@ public class DragLayer extends FrameLayout {
* @param onCompleteRunnable Optional runnable to run on animation completion.
* @param fadeOut Whether or not to fade out the view once the animation completes. If true,
* the runnable will execute after the view is faded out.
+ * @param anchorView If not null, this represents the view which the animated view stays
+ * anchored to in case scrolling is currently taking place. Note: currently this is
+ * only used for the X dimension for the case of the workspace.
*/
- public void animateView(final View view, final Rect from, final Rect to, final float finalAlpha,
- final float finalScale, int duration, final Interpolator motionInterpolator,
- final Interpolator alphaInterpolator, final Runnable onCompleteRunnable,
- final boolean fadeOut) {
+ public void animateView(final DragView view, final Rect from, final Rect to,
+ final float finalAlpha, final float initScaleX, final float initScaleY,
+ final float finalScaleX, final float finalScaleY, int duration,
+ final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
+ final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
+
// Calculate the duration of the animation based on the object's distance
final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) +
Math.pow(to.top - from.top, 2));
@@ -531,63 +540,116 @@ public class DragLayer extends FrameLayout {
if (dist < maxDist) {
duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
}
+ duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
}
- if (mDropAnim != null) {
- mDropAnim.cancel();
- }
-
- if (mFadeOutAnim != null) {
- mFadeOutAnim.cancel();
- }
-
- mDropView = view;
- final float initialAlpha = view.getAlpha();
- mDropAnim = new ValueAnimator();
+ // Fall back to cubic ease out interpolator for the animation if none is specified
+ TimeInterpolator interpolator = null;
if (alphaInterpolator == null || motionInterpolator == null) {
- mDropAnim.setInterpolator(mCubicEaseOutInterpolator);
+ interpolator = mCubicEaseOutInterpolator;
}
- mDropAnim.setDuration(duration);
- mDropAnim.setFloatValues(0.0f, 1.0f);
- mDropAnim.removeAllUpdateListeners();
- mDropAnim.addUpdateListener(new AnimatorUpdateListener() {
+ // Animate the view
+ final float initAlpha = view.getAlpha();
+ final float dropViewScale = view.getScaleX();
+ AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
+ @Override
public void onAnimationUpdate(ValueAnimator animation) {
final float percent = (Float) animation.getAnimatedValue();
- // Invalidate the old position
- int width = view.getMeasuredWidth();
- int height = view.getMeasuredHeight();
- invalidate(mDropViewPos[0], mDropViewPos[1],
- mDropViewPos[0] + width, mDropViewPos[1] + height);
+ final int width = view.getMeasuredWidth();
+ final int height = view.getMeasuredHeight();
float alphaPercent = alphaInterpolator == null ? percent :
alphaInterpolator.getInterpolation(percent);
float motionPercent = motionInterpolator == null ? percent :
motionInterpolator.getInterpolation(percent);
- mDropViewPos[0] = from.left + (int) Math.round(((to.left - from.left) * motionPercent));
- mDropViewPos[1] = from.top + (int) Math.round(((to.top - from.top) * motionPercent));
- mDropViewScale = percent * finalScale + (1 - percent);
- mDropViewAlpha = alphaPercent * finalAlpha + (1 - alphaPercent) * initialAlpha;
- invalidate(mDropViewPos[0], mDropViewPos[1],
- mDropViewPos[0] + width, mDropViewPos[1] + height);
+ float initialScaleX = initScaleX * dropViewScale;
+ float initialScaleY = initScaleY * dropViewScale;
+ float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
+ float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
+ float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
+
+ float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
+ float fromTop = from.top + (initialScaleY - 1f) * height / 2;
+
+ int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
+ int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
+
+ int xPos = x - mDropView.getScrollX() + (mAnchorView != null
+ ? (mAnchorViewInitialScrollX - mAnchorView.getScrollX()) : 0);
+ int yPos = y - mDropView.getScrollY();
+
+ mDropView.setTranslationX(xPos);
+ mDropView.setTranslationY(yPos);
+ mDropView.setScaleX(scaleX);
+ mDropView.setScaleY(scaleY);
+ mDropView.setAlpha(alpha);
}
- });
+ };
+ animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
+ anchorView);
+ }
+
+ public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
+ TimeInterpolator interpolator, final Runnable onCompleteRunnable,
+ final int animationEndStyle, View anchorView) {
+ // Clean up the previous animations
+ if (mDropAnim != null) mDropAnim.cancel();
+ if (mFadeOutAnim != null) mFadeOutAnim.cancel();
+
+ // Show the drop view if it was previously hidden
+ mDropView = view;
+ mDropView.cancelAnimation();
+ mDropView.resetLayoutParams();
+
+ // Set the anchor view if the page is scrolling
+ if (anchorView != null) {
+ mAnchorViewInitialScrollX = anchorView.getScrollX();
+ }
+ mAnchorView = anchorView;
+
+ // Create and start the animation
+ mDropAnim = new ValueAnimator();
+ mDropAnim.setInterpolator(interpolator);
+ mDropAnim.setDuration(duration);
+ mDropAnim.setFloatValues(0f, 1f);
+ mDropAnim.addUpdateListener(updateCb);
mDropAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
if (onCompleteRunnable != null) {
onCompleteRunnable.run();
}
- if (fadeOut) {
+ switch (animationEndStyle) {
+ case ANIMATION_END_DISAPPEAR:
+ clearAnimatedView();
+ break;
+ case ANIMATION_END_FADE_OUT:
fadeOutDragView();
- } else {
- mDropView = null;
+ break;
+ case ANIMATION_END_REMAIN_VISIBLE:
+ break;
}
}
});
mDropAnim.start();
}
+ public void clearAnimatedView() {
+ if (mDropAnim != null) {
+ mDropAnim.cancel();
+ }
+ if (mDropView != null) {
+ mDragController.onDeferredEndDrag(mDropView);
+ }
+ mDropView = null;
+ invalidate();
+ }
+
+ public View getAnimatedView() {
+ return mDropView;
+ }
+
private void fadeOutDragView() {
mFadeOutAnim = new ValueAnimator();
mFadeOutAnim.setDuration(150);
@@ -596,16 +658,18 @@ public class DragLayer extends FrameLayout {
mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
final float percent = (Float) animation.getAnimatedValue();
- mDropViewAlpha = 1 - percent;
- int width = mDropView.getMeasuredWidth();
- int height = mDropView.getMeasuredHeight();
- invalidate(mDropViewPos[0], mDropViewPos[1],
- mDropViewPos[0] + width, mDropViewPos[1] + height);
+
+ float alpha = 1 - percent;
+ mDropView.setAlpha(alpha);
}
});
mFadeOutAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
+ if (mDropView != null) {
+ mDragController.onDeferredEndDrag(mDropView);
+ }
mDropView = null;
+ invalidate();
}
});
mFadeOutAnim.start();
@@ -654,24 +718,4 @@ public class DragLayer extends FrameLayout {
return i;
}
}
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
- if (mDropView != null) {
- // We are animating an item that was just dropped on the home screen.
- // Render its View in the current animation position.
- canvas.save(Canvas.MATRIX_SAVE_FLAG);
- final int xPos = mDropViewPos[0] - mDropView.getScrollX();
- final int yPos = mDropViewPos[1] - mDropView.getScrollY();
- int width = mDropView.getMeasuredWidth();
- int height = mDropView.getMeasuredHeight();
- canvas.translate(xPos, yPos);
- canvas.translate((1 - mDropViewScale) * width / 2, (1 - mDropViewScale) * height / 2);
- canvas.scale(mDropViewScale, mDropViewScale);
- mDropView.setAlpha(mDropViewAlpha);
- mDropView.draw(canvas);
- canvas.restore();
- }
- }
}
diff --git a/src/com/android/launcher2/DragSource.java b/src/com/android/launcher2/DragSource.java
index 06f5ee1e6..54404770a 100644
--- a/src/com/android/launcher2/DragSource.java
+++ b/src/com/android/launcher2/DragSource.java
@@ -25,5 +25,21 @@ import com.android.launcher2.DropTarget.DragObject;
*
*/
public interface DragSource {
- void onDropCompleted(View target, DragObject d, boolean success);
+ /**
+ * @return whether items dragged from this source supports
+ */
+ boolean supportsFlingToDelete();
+
+ /**
+ * A callback specifically made back to the source after an item from this source has been flung
+ * to be deleted on a DropTarget. In such a situation, this method will be called after
+ * onDropCompleted, and more importantly, after the fling animation has completed.
+ */
+ void onFlingToDeleteCompleted();
+
+ /**
+ * A callback made back to the source after an item from this source has been dropped on a
+ * DropTarget.
+ */
+ void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, boolean success);
}
diff --git a/src/com/android/launcher2/DragView.java b/src/com/android/launcher2/DragView.java
index dd94175b6..5636f995d 100644
--- a/src/com/android/launcher2/DragView.java
+++ b/src/com/android/launcher2/DragView.java
@@ -25,6 +25,8 @@ import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
@@ -32,7 +34,10 @@ import android.view.animation.DecelerateInterpolator;
import com.android.launcher.R;
public class DragView extends View {
+ private static float sDragAlpha = 1f;
+
private Bitmap mBitmap;
+ private Bitmap mCrossFadeBitmap;
private Paint mPaint;
private int mRegistrationX;
private int mRegistrationY;
@@ -41,12 +46,12 @@ public class DragView extends View {
private Rect mDragRegion = null;
private DragLayer mDragLayer = null;
private boolean mHasDrawn = false;
+ private float mCrossFadeProgress = 0f;
ValueAnimator mAnim;
private float mOffsetX = 0.0f;
private float mOffsetY = 0.0f;
-
- private DragLayer.LayoutParams mLayoutParams;
+ private float mInitialScale = 1f;
/**
* Construct the drag view.
@@ -60,26 +65,20 @@ public class DragView extends View {
* @param registrationY The y coordinate of the registration point.
*/
public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
- int left, int top, int width, int height) {
+ int left, int top, int width, int height, final float initialScale) {
super(launcher);
mDragLayer = launcher.getDragLayer();
+ mInitialScale = initialScale;
final Resources res = getResources();
- final int dragScale = res.getInteger(R.integer.config_dragViewExtraPixels);
-
- Matrix scale = new Matrix();
- final float scaleFactor = (width + dragScale) / width;
- if (scaleFactor != 1.0f) {
- scale.setScale(scaleFactor, scaleFactor);
- }
-
- final int offsetX = res.getDimensionPixelSize(R.dimen.dragViewOffsetX);
- final int offsetY = res.getDimensionPixelSize(R.dimen.dragViewOffsetY);
+ final float offsetX = res.getDimensionPixelSize(R.dimen.dragViewOffsetX);
+ final float offsetY = res.getDimensionPixelSize(R.dimen.dragViewOffsetY);
+ final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale);
+ final float scale = (width + scaleDps) / width;
// Animate the view into the correct position
mAnim = ValueAnimator.ofFloat(0.0f, 1.0f);
- mAnim.setDuration(110);
- mAnim.setInterpolator(new DecelerateInterpolator(2.5f));
+ mAnim.setDuration(150);
mAnim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
@@ -90,19 +89,22 @@ public class DragView extends View {
mOffsetX += deltaX;
mOffsetY += deltaY;
+ setScaleX(initialScale + (value * (scale - initialScale)));
+ setScaleY(initialScale + (value * (scale - initialScale)));
+ if (sDragAlpha != 1f) {
+ setAlpha(sDragAlpha * value + (1f - value));
+ }
if (getParent() == null) {
animation.cancel();
} else {
- DragLayer.LayoutParams lp = mLayoutParams;
- lp.x += deltaX;
- lp.y += deltaY;
- mDragLayer.requestLayout();
+ setTranslationX(getTranslationX() + deltaX);
+ setTranslationY(getTranslationY() + deltaY);
}
}
});
- mBitmap = Bitmap.createBitmap(bitmap, left, top, width, height, scale, true);
+ mBitmap = Bitmap.createBitmap(bitmap, left, top, width, height);
setDragRegion(new Rect(0, 0, width, height));
// The point in our scaled bitmap that the touch events are located
@@ -112,6 +114,7 @@ public class DragView extends View {
// Force a measure, because Workspace uses getMeasuredHeight() before the layout pass
int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
measure(ms, ms);
+ mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
}
public float getOffsetY() {
@@ -150,6 +153,14 @@ public class DragView extends View {
return mDragRegion;
}
+ public float getInitialScale() {
+ return mInitialScale;
+ }
+
+ public void updateInitialScaleToCurrentScale() {
+ mInitialScale = getScaleX();
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
@@ -161,16 +172,54 @@ public class DragView extends View {
// for debugging
Paint p = new Paint();
p.setStyle(Paint.Style.FILL);
- p.setColor(0xaaffffff);
+ p.setColor(0x66ffffff);
canvas.drawRect(0, 0, getWidth(), getHeight(), p);
}
mHasDrawn = true;
+ boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
+ if (crossFade) {
+ int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255;
+ mPaint.setAlpha(alpha);
+ }
canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
+ if (crossFade) {
+ mPaint.setAlpha((int) (255 * mCrossFadeProgress));
+ canvas.save();
+ float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
+ float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
+ canvas.scale(sX, sY);
+ canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
+ canvas.restore();
+ }
+ }
+
+ public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
+ mCrossFadeBitmap = crossFadeBitmap;
+ }
+
+ public void crossFade(int duration) {
+ ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
+ va.setDuration(duration);
+ va.setInterpolator(new DecelerateInterpolator(1.5f));
+ va.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mCrossFadeProgress = animation.getAnimatedFraction();
+ }
+ });
+ va.start();
}
- public void setPaint(Paint paint) {
- mPaint = paint;
+ public void setColor(int color) {
+ if (mPaint == null) {
+ mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+ }
+ if (color != 0) {
+ mPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
+ } else {
+ mPaint.setColorFilter(null);
+ }
invalidate();
}
@@ -181,9 +230,6 @@ public class DragView extends View {
@Override
public void setAlpha(float alpha) {
super.setAlpha(alpha);
- if (mPaint == null) {
- mPaint = new Paint();
- }
mPaint.setAlpha((int) (255 * alpha));
invalidate();
}
@@ -197,17 +243,32 @@ public class DragView extends View {
*/
public void show(int touchX, int touchY) {
mDragLayer.addView(this);
+
+ // Enable hw-layers on this view
+ setLayerType(View.LAYER_TYPE_HARDWARE, null);
+
+ // Start the pick-up animation
DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
lp.width = mBitmap.getWidth();
lp.height = mBitmap.getHeight();
- lp.x = touchX - mRegistrationX;
- lp.y = touchY - mRegistrationY;
lp.customPosition = true;
setLayoutParams(lp);
- mLayoutParams = lp;
+ setTranslationX(touchX - mRegistrationX);
+ setTranslationY(touchY - mRegistrationY);
mAnim.start();
}
+ public void cancelAnimation() {
+ if (mAnim != null && mAnim.isRunning()) {
+ mAnim.cancel();
+ }
+ }
+
+ public void resetLayoutParams() {
+ mOffsetX = mOffsetY = 0;
+ requestLayout();
+ }
+
/**
* Move the window containing this view.
*
@@ -215,26 +276,17 @@ public class DragView extends View {
* @param touchY the y coordinate the user touched in DragLayer coordinates
*/
void move(int touchX, int touchY) {
- DragLayer.LayoutParams lp = mLayoutParams;
- lp.x = touchX - mRegistrationX + (int) mOffsetX;
- lp.y = touchY - mRegistrationY + (int) mOffsetY;
- mDragLayer.requestLayout();
+ setTranslationX(touchX - mRegistrationX + (int) mOffsetX);
+ setTranslationY(touchY - mRegistrationY + (int) mOffsetY);
}
void remove() {
- post(new Runnable() {
- public void run() {
- mDragLayer.removeView(DragView.this);
- }
- });
- }
+ if (getParent() != null) {
+ // Disable hw-layers on this view
+ setLayerType(View.LAYER_TYPE_NONE, null);
- int[] getPosition(int[] result) {
- DragLayer.LayoutParams lp = mLayoutParams;
- if (result == null) result = new int[2];
- result[0] = lp.x;
- result[1] = lp.y;
- return result;
+ mDragLayer.removeView(DragView.this);
+ }
}
}
diff --git a/src/com/android/launcher2/DrawableStateProxyView.java b/src/com/android/launcher2/DrawableStateProxyView.java
new file mode 100644
index 000000000..498730fdb
--- /dev/null
+++ b/src/com/android/launcher2/DrawableStateProxyView.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 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.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.android.launcher.R;
+
+public class DrawableStateProxyView extends LinearLayout {
+
+ private View mView;
+ private int mViewId;
+
+ public DrawableStateProxyView(Context context) {
+ this(context, null);
+ }
+
+ public DrawableStateProxyView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DrawableStateProxyView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DrawableStateProxyView,
+ defStyle, 0);
+ mViewId = a.getResourceId(R.styleable.DrawableStateProxyView_sourceViewId, -1);
+ a.recycle();
+
+ setFocusable(false);
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ if (mView == null) {
+ View parent = (View) getParent();
+ mView = parent.findViewById(mViewId);
+ }
+ mView.setPressed(isPressed());
+ mView.setHovered(isHovered());
+ }
+}
diff --git a/src/com/android/launcher2/DropTarget.java b/src/com/android/launcher2/DropTarget.java
index 4172da243..fb714c60a 100644
--- a/src/com/android/launcher2/DropTarget.java
+++ b/src/com/android/launcher2/DropTarget.java
@@ -16,6 +16,7 @@
package com.android.launcher2;
+import android.graphics.PointF;
import android.graphics.Rect;
/**
@@ -55,6 +56,9 @@ public interface DropTarget {
/** Indicates that the drag operation was cancelled */
public boolean cancelled = false;
+ /** Defers removing the DragView from the DragLayer until after the drop animation. */
+ public boolean deferDragViewCleanupPostAnimation = true;
+
public DragObject() {
}
}
@@ -89,6 +93,13 @@ public interface DropTarget {
void onDragExit(DragObject dragObject);
/**
+ * Handle an object being dropped as a result of flinging to delete and will be called in place
+ * of onDrop(). (This is only called on objects that are set as the DragController's
+ * fling-to-delete target.
+ */
+ void onFlingToDelete(DragObject dragObject, int x, int y, PointF vec);
+
+ /**
* Allows a DropTarget to delegate drag and drop events to another object.
*
* Most subclasses will should just return null from this method.
diff --git a/src/com/android/launcher2/FastBitmapDrawable.java b/src/com/android/launcher2/FastBitmapDrawable.java
index 9fa62da4a..d317d3302 100644
--- a/src/com/android/launcher2/FastBitmapDrawable.java
+++ b/src/com/android/launcher2/FastBitmapDrawable.java
@@ -29,7 +29,7 @@ class FastBitmapDrawable extends Drawable {
private int mAlpha;
private int mWidth;
private int mHeight;
- private final Paint mPaint = new Paint();
+ private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
FastBitmapDrawable(Bitmap b) {
mAlpha = 255;
diff --git a/src/com/android/launcher2/FocusHelper.java b/src/com/android/launcher2/FocusHelper.java
index 7807b5dcf..ecc9d9c5d 100644
--- a/src/com/android/launcher2/FocusHelper.java
+++ b/src/com/android/launcher2/FocusHelper.java
@@ -534,7 +534,7 @@ public class FocusHelper {
if (handleKeyEvent) {
// Select the first bubble text view in the current page of the workspace
final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
- final CellLayoutChildren children = layout.getChildrenLayout();
+ final ShortcutAndWidgetContainer children = layout.getShortcutsAndWidgets();
final View newIcon = getIconInDirection(layout, children, -1, 1);
if (newIcon != null) {
newIcon.requestFocus();
@@ -556,9 +556,10 @@ public class FocusHelper {
/**
* Private helper method to get the CellLayoutChildren given a CellLayout index.
*/
- private static CellLayoutChildren getCellLayoutChildrenForIndex(ViewGroup container, int i) {
+ private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
+ ViewGroup container, int i) {
ViewGroup parent = (ViewGroup) container.getChildAt(i);
- return (CellLayoutChildren) parent.getChildAt(0);
+ return (ShortcutAndWidgetContainer) parent.getChildAt(0);
}
/**
@@ -664,7 +665,7 @@ public class FocusHelper {
* Handles key events in a Workspace containing.
*/
static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
- CellLayoutChildren parent = (CellLayoutChildren) v.getParent();
+ ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
final CellLayout layout = (CellLayout) parent.getParent();
final Workspace workspace = (Workspace) layout.getParent();
final ViewGroup launcher = (ViewGroup) workspace.getParent();
@@ -819,7 +820,7 @@ public class FocusHelper {
* Handles key events for items in a Folder.
*/
static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
- CellLayoutChildren parent = (CellLayoutChildren) v.getParent();
+ ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
final CellLayout layout = (CellLayout) parent.getParent();
final Folder folder = (Folder) layout.getParent();
View title = folder.mFolderName;
diff --git a/src/com/android/launcher2/Folder.java b/src/com/android/launcher2/Folder.java
index fdde4d593..c502fb703 100644
--- a/src/com/android/launcher2/Folder.java
+++ b/src/com/android/launcher2/Folder.java
@@ -20,10 +20,9 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.InputType;
@@ -39,14 +38,13 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.launcher.R;
+import com.android.launcher2.DropTarget.DragObject;
import com.android.launcher2.FolderInfo.FolderListener;
import java.util.ArrayList;
@@ -74,18 +72,13 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
private final LayoutInflater mInflater;
private final IconCache mIconCache;
private int mState = STATE_NONE;
- private static final int FULL_GROW = 0;
- private static final int PARTIAL_GROW = 1;
private static final int REORDER_ANIMATION_DURATION = 230;
private static final int ON_EXIT_CLOSE_DELAY = 800;
- private int mMode = PARTIAL_GROW;
private boolean mRearrangeOnClose = false;
private FolderIcon mFolderIcon;
private int mMaxCountX;
private int mMaxCountY;
private int mMaxNumItems;
- private Rect mNewSize = new Rect();
- private Rect mIconRect = new Rect();
private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
private Drawable mIconDrawable;
boolean mItemsInvalidated = false;
@@ -156,7 +149,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
super.onFinishInflate();
mContent = (CellLayout) findViewById(R.id.folder_content);
mContent.setGridSize(0, 0);
- mContent.getChildrenLayout().setMotionEventSplittingEnabled(false);
+ mContent.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
mFolderName = (FolderEditText) findViewById(R.id.folder_name);
mFolderName.setFolder(this);
mFolderName.setOnFocusChangeListener(this);
@@ -365,21 +358,9 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
*/
private void positionAndSizeAsIcon() {
if (!(getParent() instanceof DragLayer)) return;
-
- DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
-
- if (mMode == PARTIAL_GROW) {
- setScaleX(0.8f);
- setScaleY(0.8f);
- setAlpha(0f);
- } else {
- mLauncher.getDragLayer().getDescendantRectRelativeToSelf(mFolderIcon, mIconRect);
- lp.width = mIconRect.width();
- lp.height = mIconRect.height();
- lp.x = mIconRect.left;
- lp.y = mIconRect.top;
- mContent.setAlpha(0);
- }
+ setScaleX(0.8f);
+ setScaleY(0.8f);
+ setAlpha(0f);
mState = STATE_SMALL;
}
@@ -387,34 +368,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
positionAndSizeAsIcon();
if (!(getParent() instanceof DragLayer)) return;
-
- ObjectAnimator oa;
- DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
-
centerAboutIcon();
- if (mMode == PARTIAL_GROW) {
- PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1);
- PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
- PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
- oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
- } else {
- PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mNewSize.width());
- PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mNewSize.height());
- PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", mNewSize.left);
- PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", mNewSize.top);
- oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
- oa.addUpdateListener(new AnimatorUpdateListener() {
- public void onAnimationUpdate(ValueAnimator animation) {
- requestLayout();
- }
- });
-
- PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
- ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
- alphaOa.setDuration(mExpandDuration);
- alphaOa.setInterpolator(new AccelerateInterpolator(2.0f));
- alphaOa.start();
- }
+ PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1);
+ PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
+ PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
+ ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
oa.addListener(new AnimatorListenerAdapter() {
@Override
@@ -457,33 +415,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
public void animateClosed() {
if (!(getParent() instanceof DragLayer)) return;
-
- ObjectAnimator oa;
- if (mMode == PARTIAL_GROW) {
- PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
- PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f);
- PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f);
- oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
- } else {
- DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
-
- PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mIconRect.width());
- PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mIconRect.height());
- PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", mIconRect.left);
- PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", mIconRect.top);
- oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y);
- oa.addUpdateListener(new AnimatorUpdateListener() {
- public void onAnimationUpdate(ValueAnimator animation) {
- requestLayout();
- }
- });
-
- PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
- ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha);
- alphaOa.setDuration(mExpandDuration);
- alphaOa.setInterpolator(new DecelerateInterpolator(2.0f));
- alphaOa.start();
- }
+ PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
+ PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f);
+ PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f);
+ ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
oa.addListener(new AnimatorListenerAdapter() {
@Override
@@ -594,7 +529,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
for (int x = startX; x <= endX; x++) {
View v = mContent.getChildAt(x,y);
if (mContent.animateChildToPosition(v, empty[0], empty[1],
- REORDER_ANIMATION_DURATION, delay)) {
+ REORDER_ANIMATION_DURATION, delay, true, true)) {
empty[0] = x;
empty[1] = y;
delay += delayAmount;
@@ -611,7 +546,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
for (int x = startX; x >= endX; x--) {
View v = mContent.getChildAt(x,y);
if (mContent.animateChildToPosition(v, empty[0], empty[1],
- REORDER_ANIMATION_DURATION, delay)) {
+ REORDER_ANIMATION_DURATION, delay, true, true)) {
empty[0] = x;
empty[1] = y;
delay += delayAmount;
@@ -685,7 +620,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
mReorderAlarm.cancelAlarm();
}
- public void onDropCompleted(View target, DragObject d, boolean success) {
+ public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
+ boolean success) {
if (success) {
if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon) {
replaceFolderWithFinalItem();
@@ -719,6 +655,20 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
updateItemLocationsInDatabase();
}
+ @Override
+ public boolean supportsFlingToDelete() {
+ return true;
+ }
+
+ public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+ // Do nothing
+ }
+
+ @Override
+ public void onFlingToDeleteCompleted() {
+ // Do nothing
+ }
+
private void updateItemLocationsInDatabase() {
ArrayList<View> list = getItemsInReadingOrder();
for (int i = 0; i < list.size(); i++) {
@@ -791,11 +741,17 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
int centeredLeft = centerX - width / 2;
int centeredTop = centerY - height / 2;
+ int currentPage = mLauncher.getWorkspace().getCurrentPage();
+ // In case the workspace is scrolling, we need to use the final scroll to compute
+ // the folders bounds.
+ mLauncher.getWorkspace().setFinalScrollForPageChange(currentPage);
// We first fetch the currently visible CellLayoutChildren
- CellLayout currentPage = mLauncher.getWorkspace().getCurrentDropLayout();
- CellLayoutChildren boundingLayout = currentPage.getChildrenLayout();
+ CellLayout currentLayout = (CellLayout) mLauncher.getWorkspace().getChildAt(currentPage);
+ ShortcutAndWidgetContainer boundingLayout = currentLayout.getShortcutsAndWidgets();
Rect bounds = new Rect();
parent.getDescendantRectRelativeToSelf(boundingLayout, bounds);
+ // We reset the workspaces scroll
+ mLauncher.getWorkspace().resetFinalScrollForPageChange(currentPage);
// We need to bound the folder to the currently visible CellLayoutChildren
int left = Math.min(Math.max(bounds.left, centeredLeft),
@@ -821,14 +777,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
mFolderIcon.setPivotX(folderIconPivotX);
mFolderIcon.setPivotY(folderIconPivotY);
- if (mMode == PARTIAL_GROW) {
- lp.width = width;
- lp.height = height;
- lp.x = left;
- lp.y = top;
- } else {
- mNewSize.set(left, top, left + width, top + height);
- }
+ lp.width = width;
+ lp.height = height;
+ lp.x = left;
+ lp.y = top;
}
private void setupContentForNumItems(int count) {
@@ -886,11 +838,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
}
public int getItemCount() {
- return mContent.getChildrenLayout().getChildCount();
+ return mContent.getShortcutsAndWidgets().getChildCount();
}
public View getItemAt(int index) {
- return mContent.getChildrenLayout().getChildAt(index);
+ return mContent.getShortcutsAndWidgets().getChildAt(index);
}
private void onCloseComplete() {
diff --git a/src/com/android/launcher2/FolderIcon.java b/src/com/android/launcher2/FolderIcon.java
index 3c0829d4a..c005edfcd 100644
--- a/src/com/android/launcher2/FolderIcon.java
+++ b/src/com/android/launcher2/FolderIcon.java
@@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
@@ -53,6 +54,8 @@ public class FolderIcon extends LinearLayout implements FolderListener {
FolderInfo mInfo;
private static boolean sStaticValuesDirty = true;
+ private CheckLongPressHelper mLongPressHelper;
+
// The number of icons to display in the
private static final int NUM_ITEMS_IN_PREVIEW = 3;
private static final int CONSUMPTION_ANIMATION_DURATION = 100;
@@ -95,10 +98,16 @@ public class FolderIcon extends LinearLayout implements FolderListener {
public FolderIcon(Context context, AttributeSet attrs) {
super(context, attrs);
+ init();
}
public FolderIcon(Context context) {
super(context);
+ init();
+ }
+
+ private void init() {
+ mLongPressHelper = new CheckLongPressHelper(this);
}
public boolean isDropEnabled() {
@@ -295,14 +304,14 @@ public class FolderIcon extends LinearLayout implements FolderListener {
}
public void performCreateAnimation(final ShortcutInfo destInfo, final View destView,
- final ShortcutInfo srcInfo, final View srcView, Rect dstRect,
+ final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect,
float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {
Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), destView.getMeasuredWidth());
// This will animate the dragView (srcView) into the new folder
- onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable);
+ onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable, null);
// This will animate the first item from it's position as an icon into its
// position as the first item in the preview
@@ -320,8 +329,9 @@ public class FolderIcon extends LinearLayout implements FolderListener {
mFolderRingAnimator.animateToNaturalState();
}
- private void onDrop(final ShortcutInfo item, View animateView, Rect finalRect,
- float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable) {
+ private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect,
+ float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable,
+ DragObject d) {
item.cellX = -1;
item.cellY = -1;
@@ -359,10 +369,11 @@ public class FolderIcon extends LinearLayout implements FolderListener {
float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f;
+ float finalScale = scale * scaleRelativeToDragLayer;
dragLayer.animateView(animateView, from, to, finalAlpha,
- scale * scaleRelativeToDragLayer, DROP_IN_ANIMATION_DURATION,
+ 1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
new DecelerateInterpolator(2), new AccelerateInterpolator(2),
- postAnimationRunnable, false);
+ postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
postDelayed(new Runnable() {
public void run() {
addItem(item);
@@ -382,7 +393,7 @@ public class FolderIcon extends LinearLayout implements FolderListener {
item = (ShortcutInfo) d.dragInfo;
}
mFolder.notifyDrop();
- onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable);
+ onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d);
}
public DropTarget getDropTargetDelegate(DragObject d) {
@@ -589,4 +600,29 @@ public class FolderIcon extends LinearLayout implements FolderListener {
setContentDescription(String.format(mContext.getString(R.string.folder_name_format),
title));
}
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // Call the superclass onTouchEvent first, because sometimes it changes the state to
+ // isPressed() on an ACTION_UP
+ boolean result = super.onTouchEvent(event);
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mLongPressHelper.postCheckForLongPress();
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mLongPressHelper.cancelLongPress();
+ break;
+ }
+ return result;
+ }
+
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+
+ mLongPressHelper.cancelLongPress();
+ }
}
diff --git a/src/com/android/launcher2/HandleView.java b/src/com/android/launcher2/HandleView.java
index 13d07e2dd..d77138b0a 100644
--- a/src/com/android/launcher2/HandleView.java
+++ b/src/com/android/launcher2/HandleView.java
@@ -17,13 +17,12 @@
package com.android.launcher2;
-import android.widget.ImageView;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.MotionEvent;
-import android.view.KeyEvent;
import android.view.View;
+import android.widget.ImageView;
import com.android.launcher.R;
diff --git a/src/com/android/launcher2/HolographicPagedViewIcon.java b/src/com/android/launcher2/HolographicPagedViewIcon.java
deleted file mode 100644
index dda233e37..000000000
--- a/src/com/android/launcher2/HolographicPagedViewIcon.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.widget.TextView;
-
-
-
-/**
- * An icon on a PagedView, specifically for items in the launcher's paged view (with compound
- * drawables on the top).
- */
-public class HolographicPagedViewIcon extends TextView {
- PagedViewIcon mOriginalIcon;
- Paint mPaint;
-
- public HolographicPagedViewIcon(Context context, PagedViewIcon original) {
- super(context);
- mOriginalIcon = original;
- mPaint = new Paint();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- Bitmap overlay = mOriginalIcon.getHolographicOutline();
- if (overlay != null) {
- final int offset = getScrollX();
- final int compoundPaddingLeft = getCompoundPaddingLeft();
- final int compoundPaddingRight = getCompoundPaddingRight();
- int hspace = getWidth() - compoundPaddingRight - compoundPaddingLeft;
- canvas.drawBitmap(overlay,
- offset + compoundPaddingLeft + (hspace - overlay.getWidth()) / 2,
- mOriginalIcon.getPaddingTop(),
- mPaint);
- }
- }
-}
diff --git a/src/com/android/launcher2/Hotseat.java b/src/com/android/launcher2/Hotseat.java
index f7fa38007..9e525bd5a 100644
--- a/src/com/android/launcher2/Hotseat.java
+++ b/src/com/android/launcher2/Hotseat.java
@@ -29,13 +29,13 @@ import com.android.launcher.R;
public class Hotseat extends FrameLayout {
private static final String TAG = "Hotseat";
- private static final int sAllAppsButtonRank = 2; // In the middle of the dock
private Launcher mLauncher;
private CellLayout mContent;
private int mCellCountX;
private int mCellCountY;
+ private int mAllAppsButtonRank;
private boolean mIsLandscape;
public Hotseat(Context context) {
@@ -53,6 +53,7 @@ public class Hotseat extends FrameLayout {
R.styleable.Hotseat, defStyle, 0);
mCellCountX = a.getInt(R.styleable.Hotseat_cellCountX, -1);
mCellCountY = a.getInt(R.styleable.Hotseat_cellCountY, -1);
+ mAllAppsButtonRank = context.getResources().getInteger(R.integer.hotseat_all_apps_index);
mIsLandscape = context.getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE;
}
@@ -77,8 +78,8 @@ public class Hotseat extends FrameLayout {
int getCellYFromOrder(int rank) {
return mIsLandscape ? (mContent.getCountY() - (rank + 1)) : 0;
}
- public static boolean isAllAppsButtonRank(int rank) {
- return rank == sAllAppsButtonRank;
+ public boolean isAllAppsButtonRank(int rank) {
+ return rank == mAllAppsButtonRank;
}
@Override
@@ -88,6 +89,7 @@ public class Hotseat extends FrameLayout {
if (mCellCountY < 0) mCellCountY = LauncherModel.getCellCountY();
mContent = (CellLayout) findViewById(R.id.layout);
mContent.setGridSize(mCellCountX, mCellCountY);
+ mContent.setIsHotseat(true);
resetLayout();
}
@@ -126,9 +128,10 @@ public class Hotseat extends FrameLayout {
// Note: We do this to ensure that the hotseat is always laid out in the orientation of
// the hotseat in order regardless of which orientation they were added
- int x = getCellXFromOrder(sAllAppsButtonRank);
- int y = getCellYFromOrder(sAllAppsButtonRank);
- mContent.addViewToCellLayout(allAppsButton, -1, 0, new CellLayout.LayoutParams(x,y,1,1),
- true);
+ int x = getCellXFromOrder(mAllAppsButtonRank);
+ int y = getCellYFromOrder(mAllAppsButtonRank);
+ CellLayout.LayoutParams lp = new CellLayout.LayoutParams(x,y,1,1);
+ lp.canReorder = false;
+ mContent.addViewToCellLayout(allAppsButton, -1, 0, lp, true, true);
}
}
diff --git a/src/com/android/launcher2/IconCache.java b/src/com/android/launcher2/IconCache.java
index 2430a6b33..1e8379d3c 100644
--- a/src/com/android/launcher2/IconCache.java
+++ b/src/com/android/launcher2/IconCache.java
@@ -16,7 +16,9 @@
package com.android.launcher2;
+import android.app.ActivityManager;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -24,7 +26,6 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
-import android.util.DisplayMetrics;
import java.util.HashMap;
@@ -49,23 +50,13 @@ public class IconCache {
private int mIconDpi;
public IconCache(LauncherApplication context) {
+ ActivityManager activityManager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+
mContext = context;
mPackageManager = context.getPackageManager();
- int density = context.getResources().getDisplayMetrics().densityDpi;
- if (LauncherApplication.isScreenLarge()) {
- if (density == DisplayMetrics.DENSITY_LOW) {
- mIconDpi = DisplayMetrics.DENSITY_MEDIUM;
- } else if (density == DisplayMetrics.DENSITY_MEDIUM) {
- mIconDpi = DisplayMetrics.DENSITY_HIGH;
- } else if (density == DisplayMetrics.DENSITY_HIGH) {
- mIconDpi = DisplayMetrics.DENSITY_XHIGH;
- } else if (density == DisplayMetrics.DENSITY_XHIGH) {
- // We'll need to use a denser icon, or some sort of a mipmap
- mIconDpi = DisplayMetrics.DENSITY_XHIGH;
- }
- } else {
- mIconDpi = context.getResources().getDisplayMetrics().densityDpi;
- }
+ mIconDpi = activityManager.getLauncherLargeIconDensity();
+
// need to set mIconDpi before getting default icon
mDefaultIcon = makeDefaultIcon();
}
diff --git a/src/com/android/launcher2/InfoDropTarget.java b/src/com/android/launcher2/InfoDropTarget.java
index dba845b04..2e0b5c827 100644
--- a/src/com/android/launcher2/InfoDropTarget.java
+++ b/src/com/android/launcher2/InfoDropTarget.java
@@ -21,8 +21,6 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.TransitionDrawable;
import android.util.AttributeSet;
import android.view.View;
@@ -34,7 +32,6 @@ public class InfoDropTarget extends ButtonDropTarget {
private ColorStateList mOriginalTextColor;
private TransitionDrawable mDrawable;
- private int mHoverColor = 0xFF0000FF;
public InfoDropTarget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -53,8 +50,6 @@ public class InfoDropTarget extends ButtonDropTarget {
// Get the hover color
Resources r = getResources();
mHoverColor = r.getColor(R.color.info_target_hover_tint);
- mHoverPaint.setColorFilter(new PorterDuffColorFilter(
- mHoverColor, PorterDuff.Mode.SRC_ATOP));
mDrawable = (TransitionDrawable) getCompoundDrawables()[0];
mDrawable.setCrossFadeEnabled(true);
@@ -85,6 +80,9 @@ public class InfoDropTarget extends ButtonDropTarget {
if (componentName != null) {
mLauncher.startApplicationDetailsActivity(componentName);
}
+
+ // There is no post-drop animation, so clean up the DragView now
+ d.deferDragViewCleanupPostAnimation = false;
return false;
}
diff --git a/src/com/android/launcher2/InstallShortcutReceiver.java b/src/com/android/launcher2/InstallShortcutReceiver.java
index ed8f29942..4c0974fd3 100644
--- a/src/com/android/launcher2/InstallShortcutReceiver.java
+++ b/src/com/android/launcher2/InstallShortcutReceiver.java
@@ -16,18 +16,32 @@
package com.android.launcher2;
-import java.util.ArrayList;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.widget.Toast;
import com.android.launcher.R;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
public class InstallShortcutReceiver extends BroadcastReceiver {
public static final String ACTION_INSTALL_SHORTCUT =
"com.android.launcher.action.INSTALL_SHORTCUT";
+ public static final String NEW_APPS_PAGE_KEY = "apps.new.page";
+ public static final String NEW_APPS_LIST_KEY = "apps.new.list";
+
+ public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
+ public static final int NEW_SHORTCUT_STAGGER_DELAY = 75;
+
+ private static final int INSTALL_SHORTCUT_SUCCESSFUL = 0;
+ private static final int INSTALL_SHORTCUT_IS_DUPLICATE = -1;
+ private static final int INSTALL_SHORTCUT_NO_SPACE = -2;
// A mime-type representing shortcut data
public static final String SHORTCUT_MIMETYPE =
@@ -39,22 +53,58 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
return;
}
+ String spKey = LauncherApplication.getSharedPreferencesKey();
+ SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+
+ final int screen = Launcher.getScreen();
+ final Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+ if (intent == null) {
+ return;
+ }
+ // This name is only used for comparisons and notifications, so fall back to activity name
+ // if not supplied
+ String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+ if (name == null) {
+ try {
+ PackageManager pm = context.getPackageManager();
+ ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
+ name = info.loadLabel(pm).toString();
+ } catch (PackageManager.NameNotFoundException nnfe) {
+ return;
+ }
+ }
- int screen = Launcher.getScreen();
+ final ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context);
+ final boolean exists = LauncherModel.shortcutExists(context, name, intent);
+ final int[] result = {INSTALL_SHORTCUT_SUCCESSFUL};
+
+ // Try adding the target to the workspace screens incrementally, starting at the current
+ // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1))
+ boolean found = false;
+ for (int i = 0; i < (2 * Launcher.SCREEN_COUNT) + 1 && !found; ++i) {
+ int si = screen + (int) ((i / 2f) + 0.5f) * ((i % 2 == 1) ? 1 : -1);
+ if (0 <= si && si < Launcher.SCREEN_COUNT) {
+ found = installShortcut(context, data, items, name, intent, si, exists, sp, result);
+ }
+ }
- if (!installShortcut(context, data, screen)) {
- // The target screen is full, let's try the other screens
- for (int i = 0; i < Launcher.SCREEN_COUNT; i++) {
- if (i != screen && installShortcut(context, data, i)) break;
+ // We only report error messages (duplicate shortcut or out of space) as the add-animation
+ // will provide feedback otherwise
+ if (!found) {
+ if (result[0] == INSTALL_SHORTCUT_NO_SPACE) {
+ Toast.makeText(context, context.getString(R.string.out_of_space),
+ Toast.LENGTH_SHORT).show();
+ } else if (result[0] == INSTALL_SHORTCUT_IS_DUPLICATE) {
+ Toast.makeText(context, context.getString(R.string.shortcut_duplicate, name),
+ Toast.LENGTH_SHORT).show();
}
}
}
- private boolean installShortcut(Context context, Intent data, int screen) {
- String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-
- if (findEmptyCell(context, mCoordinates, screen)) {
- Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+ private boolean installShortcut(Context context, Intent data, ArrayList<ItemInfo> items,
+ String name, Intent intent, int screen, boolean shortcutExists,
+ SharedPreferences sharedPrefs, int[] result) {
+ if (findEmptyCell(context, items, mCoordinates, screen)) {
if (intent != null) {
if (intent.getAction() == null) {
intent.setAction(Intent.ACTION_VIEW);
@@ -63,38 +113,47 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
// By default, we allow for duplicate entries (located in
// different places)
boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
- if (duplicate || !LauncherModel.shortcutExists(context, name, intent)) {
+ if (duplicate || !shortcutExists) {
+ // If the new app is going to fall into the same page as before, then just
+ // continue adding to the current page
+ int newAppsScreen = sharedPrefs.getInt(NEW_APPS_PAGE_KEY, screen);
+ Set<String> newApps = new HashSet<String>();
+ if (newAppsScreen == screen) {
+ newApps = sharedPrefs.getStringSet(NEW_APPS_LIST_KEY, newApps);
+ }
+ newApps.add(intent.toUri(0).toString());
+ sharedPrefs.edit()
+ .putInt(NEW_APPS_PAGE_KEY, screen)
+ .putStringSet(NEW_APPS_LIST_KEY, newApps)
+ .commit();
+
+ // Update the Launcher db
LauncherApplication app = (LauncherApplication) context.getApplicationContext();
ShortcutInfo info = app.getModel().addShortcut(context, data,
- LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, mCoordinates[0],
- mCoordinates[1], true);
- if (info != null) {
- Toast.makeText(context, context.getString(R.string.shortcut_installed, name),
- Toast.LENGTH_SHORT).show();
- } else {
+ LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
+ mCoordinates[0], mCoordinates[1], true);
+ if (info == null) {
return false;
}
} else {
- Toast.makeText(context, context.getString(R.string.shortcut_duplicate, name),
- Toast.LENGTH_SHORT).show();
+ result[0] = INSTALL_SHORTCUT_IS_DUPLICATE;
}
return true;
}
} else {
- Toast.makeText(context, context.getString(R.string.out_of_space),
- Toast.LENGTH_SHORT).show();
+ result[0] = INSTALL_SHORTCUT_NO_SPACE;
}
return false;
}
- private static boolean findEmptyCell(Context context, int[] xy, int screen) {
+ private static boolean findEmptyCell(Context context, ArrayList<ItemInfo> items, int[] xy,
+ int screen) {
final int xCount = LauncherModel.getCellCountX();
final int yCount = LauncherModel.getCellCountY();
boolean[][] occupied = new boolean[xCount][yCount];
- ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context);
ItemInfo item = null;
int cellX, cellY, spanX, spanY;
for (int i = 0; i < items.size(); ++i) {
diff --git a/src/com/android/launcher2/InstallWidgetReceiver.java b/src/com/android/launcher2/InstallWidgetReceiver.java
index 6b3763ce0..a1e9b1187 100644
--- a/src/com/android/launcher2/InstallWidgetReceiver.java
+++ b/src/com/android/launcher2/InstallWidgetReceiver.java
@@ -189,7 +189,7 @@ public class InstallWidgetReceiver {
final PendingAddWidgetInfo createInfo = new PendingAddWidgetInfo(widgetInfo, mMimeType,
mClipData);
mLauncher.addAppWidgetFromDrop(createInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP,
- mTargetLayoutScreen, null, mTargetLayoutPos);
+ mTargetLayoutScreen, null, null, mTargetLayoutPos);
}
}
}
diff --git a/src/com/android/launcher2/ItemInfo.java b/src/com/android/launcher2/ItemInfo.java
index 8d4662495..11a6c0d00 100644
--- a/src/com/android/launcher2/ItemInfo.java
+++ b/src/com/android/launcher2/ItemInfo.java
@@ -77,6 +77,15 @@ class ItemInfo {
int spanY = 1;
/**
+ * Indicates the minimum X cell span.
+ */
+ int minSpanX = 1;
+
+ /**
+ * Indicates the minimum Y cell span.
+ */
+ int minSpanY = 1;
+ /**
* Indicates whether the item is a gesture.
*/
boolean isGesture = false;
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 8e074b53f..b3ce96884 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -23,12 +23,12 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.SearchManager;
-import android.app.StatusBarManager;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
@@ -54,8 +54,8 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Build;
import android.os.Bundle;
+import android.os.Debug;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
@@ -80,10 +80,12 @@ import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
+import android.view.animation.BounceInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.Advanceable;
@@ -103,7 +105,12 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
/**
* Default launcher application.
@@ -139,6 +146,7 @@ public final class Launcher extends Activity
static final int DIALOG_RENAME_FOLDER = 2;
private static final String PREFERENCES = "launcher.preferences";
+ static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher.force_enable_rotation";
// Type: int
private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
@@ -246,7 +254,16 @@ public final class Launcher extends Activity
private static Drawable.ConstantState[] sAppMarketIcon = new Drawable.ConstantState[2];
static final ArrayList<String> sDumpLogs = new ArrayList<String>();
+ PendingAddWidgetInfo mWidgetBeingConfigured = null;
+ // We only want to get the SharedPreferences once since it does an FS stat each time we get
+ // it from the context.
+ private SharedPreferences mSharedPrefs;
+
+ // Holds the page that we need to animate to, and the icon views that we need to animate up
+ // when we scroll to that page on resume.
+ private int mNewShortcutAnimatePage = -1;
+ private ArrayList<View> mNewShortcutAnimateViews = new ArrayList<View>();
private BubbleTextView mWaitingForResume;
@@ -274,6 +291,8 @@ public final class Launcher extends Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LauncherApplication app = ((LauncherApplication)getApplication());
+ mSharedPrefs = getSharedPreferences(LauncherApplication.getSharedPreferencesKey(),
+ Context.MODE_PRIVATE);
mModel = app.setLauncher(this);
mIconCache = app.getIconCache();
mDragController = new DragController(this);
@@ -310,7 +329,7 @@ public final class Launcher extends Activity
}
if (!mRestoring) {
- mModel.startLoader(this, true);
+ mModel.startLoader(true);
}
if (!mModel.isAllAppsLoaded()) {
@@ -348,8 +367,11 @@ public final class Launcher extends Activity
}
mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
+ final String forceEnableRotation =
+ SystemProperties.get(FORCE_ENABLE_ROTATION_PROPERTY, "false");
+
// On large interfaces, we want the screen to auto-rotate based on the current orientation
- if (LauncherApplication.isScreenLarge() || Build.TYPE.contentEquals("eng")) {
+ if (LauncherApplication.isScreenLarge() || "true".equalsIgnoreCase(forceEnableRotation)) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
}
@@ -495,24 +517,35 @@ public final class Launcher extends Activity
break;
case REQUEST_CREATE_APPWIDGET:
int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
- completeAddAppWidget(appWidgetId, args.container, args.screen);
+ completeAddAppWidget(appWidgetId, args.container, args.screen, null, null);
result = true;
break;
case REQUEST_PICK_WALLPAPER:
// We just wanted the activity result here so we can clear mWaitingForResult
break;
}
- // In any situation where we have a multi-step drop, we should reset the add info only after
- // we complete the drop
+ // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
+ // if you turned the screen off and then back while in All Apps, Launcher would not
+ // return to the workspace. Clearing mAddInfo.container here fixes this issue
resetAddInfo();
return result;
}
@Override
- protected void onActivityResult(final int requestCode, int resultCode, final Intent data) {
+ protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
boolean delayExitSpringLoadedMode = false;
+ boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
+ requestCode == REQUEST_CREATE_APPWIDGET);
mWaitingForResult = false;
+ // We have special handling for widgets
+ if (isWidgetDrop) {
+ int appWidgetId = data != null ?
+ data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
+ completeTwoStageWidgetDrop(resultCode, appWidgetId);
+ return;
+ }
+
// The pattern used here is that a user PICKs a specific application,
// which, depending on the target, might need to CREATE the actual target.
@@ -526,71 +559,75 @@ public final class Launcher extends Activity
args.screen = mPendingAddInfo.screen;
args.cellX = mPendingAddInfo.cellX;
args.cellY = mPendingAddInfo.cellY;
-
- // If the loader is still running, defer the add until it is done.
if (isWorkspaceLocked()) {
sPendingAddList.add(args);
} else {
delayExitSpringLoadedMode = completeAdd(args);
}
- } else if ((requestCode == REQUEST_PICK_APPWIDGET ||
- requestCode == REQUEST_CREATE_APPWIDGET) && resultCode == RESULT_CANCELED) {
- if (data != null) {
- // Clean up the appWidgetId if we canceled
- int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
- if (appWidgetId != -1) {
- mAppWidgetHost.deleteAppWidgetId(appWidgetId);
- }
- }
}
-
+ mDragLayer.clearAnimatedView();
// Exit spring loaded mode if necessary after cancelling the configuration of a widget
- exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode);
+ exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode,
+ null);
+ }
+
+ private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
+ CellLayout cellLayout = (CellLayout) mWorkspace.getChildAt(mWidgetBeingConfigured.screen);
+ Runnable onCompleteRunnable = null;
+ int animationType = 0;
+
+ if (resultCode == RESULT_OK) {
+ animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
+ final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
+ mWidgetBeingConfigured.info);
+ mWidgetBeingConfigured.boundWidget = layout;
+ onCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
+ mPendingAddInfo.screen, layout, null);
+ exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
+ null);
+ }
+ };
+ } else if (resultCode == RESULT_CANCELED) {
+ animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
+ onCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
+ null);
+ }
+ };
+ }
+ mWorkspace.animateWidgetDrop(mWidgetBeingConfigured, cellLayout,
+ (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
+ animationType, mWidgetBeingConfigured.boundWidget, true);
+ mWidgetBeingConfigured = null;
}
@Override
protected void onResume() {
super.onResume();
+
mPaused = false;
if (mRestoring || mOnResumeNeedsLoad) {
mWorkspaceLoading = true;
- mModel.startLoader(this, true);
+ mModel.startLoader(true);
mRestoring = false;
mOnResumeNeedsLoad = false;
}
+
+ // Reset the pressed state of icons that were locked in the press state while activities
+ // were launching
if (mWaitingForResume != null) {
+ // Resets the previous workspace icon press state
mWaitingForResume.setStayPressed(false);
}
- // When we resume Launcher, a different Activity might be responsible for the app
- // market intent, so refresh the icon
- updateAppMarketIcon();
- mAppsCustomizeTabHost.onResume();
- if (!mWorkspaceLoading) {
- final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
- final Workspace workspace = mWorkspace;
- // We want to let Launcher draw itself at least once before we force it to build
- // layers on all the workspace pages, so that transitioning to Launcher from other
- // apps is nice and speedy. Usually the first call to preDraw doesn't correspond to
- // a true draw so we wait until the second preDraw call to be safe
- observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- boolean mFirstTime = true;
- public boolean onPreDraw() {
- if (mFirstTime) {
- mFirstTime = false;
- } else {
- // We delay the layer building a bit in order to give
- // other message processing a time to run. In particular
- // this avoids a delay in hiding the IME if it was
- // currently shown, because doing that may involve
- // some communication back with the app.
- workspace.postDelayed(mBuildLayersRunnable, 500);
- observer.removeOnPreDrawListener(this);
- }
- return true;
- }
- });
+ if (mAppsCustomizeContent != null) {
+ // Resets the previous all apps icon press state
+ mAppsCustomizeContent.resetDrawableState();
}
- clearTypedText();
}
@Override
@@ -705,7 +742,7 @@ public final class Launcher extends Activity
showAllApps(false);
}
- final int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
+ int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
if (currentScreen > -1) {
mWorkspace.setCurrentPage(currentScreen);
}
@@ -848,7 +885,7 @@ public final class Launcher extends Activity
cellXY[0] = cellX;
cellXY[1] = cellY;
} else if (!layout.findCellForSpan(cellXY, 1, 1)) {
- showOutOfSpaceMessage();
+ showOutOfSpaceMessage(isHotseatLayout(layout));
return;
}
@@ -892,13 +929,14 @@ public final class Launcher extends Activity
foundCellSpan = true;
// If appropriate, either create a folder or add to an existing folder
- if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY,
+ if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
true, null,null)) {
return;
}
DragObject dragObject = new DragObject();
dragObject.dragInfo = info;
- if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, dragObject, true)) {
+ if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
+ true)) {
return;
}
} else if (touchXY != null) {
@@ -910,7 +948,7 @@ public final class Launcher extends Activity
}
if (!foundCellSpan) {
- showOutOfSpaceMessage();
+ showOutOfSpaceMessage(isHotseatLayout(layout));
return;
}
@@ -939,7 +977,7 @@ public final class Launcher extends Activity
return getSpanForWidget(info.provider, info.minWidth, info.minHeight, spanXY);
}
- int[] getMinResizeSpanForWidget(AppWidgetProviderInfo info, int[] spanXY) {
+ int[] getMinSpanForWidget(AppWidgetProviderInfo info, int[] spanXY) {
return getSpanForWidget(info.provider, info.minResizeWidth, info.minResizeHeight, spanXY);
}
@@ -947,18 +985,27 @@ public final class Launcher extends Activity
return getSpanForWidget(info.componentName, info.minWidth, info.minHeight, spanXY);
}
+ int[] getMinSpanForWidget(PendingAddWidgetInfo info, int[] spanXY) {
+ return getSpanForWidget(info.componentName, info.minResizeWidth,
+ info.minResizeHeight, spanXY);
+ }
+
/**
* Add a widget to the workspace.
*
* @param appWidgetId The app widget id
* @param cellInfo The position on screen where to create the widget.
*/
- private void completeAddAppWidget(final int appWidgetId, long container, int screen) {
- AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+ private void completeAddAppWidget(final int appWidgetId, long container, int screen,
+ AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo) {
+ if (appWidgetInfo == null) {
+ appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+ }
// Calculate the grid spans needed to fit this widget
CellLayout layout = getCellLayout(container, screen);
+ int[] minSpanXY = getMinSpanForWidget(appWidgetInfo, null);
int[] spanXY = getSpanForWidget(appWidgetInfo, null);
// Try finding open space on Launcher screen
@@ -966,18 +1013,24 @@ public final class Launcher extends Activity
// if we are placing widgets on a "spring-loaded" screen
int[] cellXY = mTmpAddItemCellCoordinates;
int[] touchXY = mPendingAddInfo.dropPos;
+ int[] finalSpan = new int[2];
boolean foundCellSpan = false;
if (mPendingAddInfo.cellX >= 0 && mPendingAddInfo.cellY >= 0) {
cellXY[0] = mPendingAddInfo.cellX;
cellXY[1] = mPendingAddInfo.cellY;
+ spanXY[0] = mPendingAddInfo.spanX;
+ spanXY[1] = mPendingAddInfo.spanY;
foundCellSpan = true;
} else if (touchXY != null) {
// when dragging and dropping, just find the closest free spot
int[] result = layout.findNearestVacantArea(
- touchXY[0], touchXY[1], spanXY[0], spanXY[1], cellXY);
+ touchXY[0], touchXY[1], minSpanXY[0], minSpanXY[1], spanXY[0],
+ spanXY[1], cellXY, finalSpan);
+ spanXY[0] = finalSpan[0];
+ spanXY[1] = finalSpan[1];
foundCellSpan = (result != null);
} else {
- foundCellSpan = layout.findCellForSpan(cellXY, spanXY[0], spanXY[1]);
+ foundCellSpan = layout.findCellForSpan(cellXY, minSpanXY[0], minSpanXY[1]);
}
if (!foundCellSpan) {
@@ -990,7 +1043,7 @@ public final class Launcher extends Activity
}
}.start();
}
- showOutOfSpaceMessage();
+ showOutOfSpaceMessage(isHotseatLayout(layout));
return;
}
@@ -998,22 +1051,30 @@ public final class Launcher extends Activity
LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId);
launcherInfo.spanX = spanXY[0];
launcherInfo.spanY = spanXY[1];
+ launcherInfo.minSpanX = mPendingAddInfo.minSpanX;
+ launcherInfo.minSpanY = mPendingAddInfo.minSpanY;
LauncherModel.addItemToDatabase(this, launcherInfo,
container, screen, cellXY[0], cellXY[1], false);
if (!mRestoring) {
- // Perform actual inflation because we're live
- launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+ if (hostView == null) {
+ // Perform actual inflation because we're live
+ launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+ launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
+ } else {
+ // The AppWidgetHostView has already been inflated and instantiated
+ launcherInfo.hostView = hostView;
+ }
- launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
launcherInfo.hostView.setTag(launcherInfo);
-
+ launcherInfo.hostView.setVisibility(View.VISIBLE);
mWorkspace.addInScreen(launcherInfo.hostView, container, screen, cellXY[0], cellXY[1],
launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
}
+ resetAddInfo();
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -1068,6 +1129,35 @@ public final class Launcher extends Activity
public void onWindowVisibilityChanged(int visibility) {
mVisible = visibility == View.VISIBLE;
updateRunning();
+ // The following code used to be in onResume, but it turns out onResume is called when
+ // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
+ // is a more appropriate event to handle
+ if (mVisible) {
+ mAppsCustomizeTabHost.onWindowVisible();
+ if (!mWorkspaceLoading) {
+ final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
+ // We want to let Launcher draw itself at least once before we force it to build
+ // layers on all the workspace pages, so that transitioning to Launcher from other
+ // apps is nice and speedy. Usually the first call to preDraw doesn't correspond to
+ // a true draw so we wait until the second preDraw call to be safe
+ observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ public boolean onPreDraw() {
+ // We delay the layer building a bit in order to give
+ // other message processing a time to run. In particular
+ // this avoids a delay in hiding the IME if it was
+ // currently shown, because doing that may involve
+ // some communication back with the app.
+ mWorkspace.postDelayed(mBuildLayersRunnable, 500);
+ observer.removeOnPreDrawListener(this);
+ return true;
+ }
+ });
+ }
+ // When Launcher comes back to foreground, a different Activity might be responsible for
+ // the app market intent, so refresh the icon
+ updateAppMarketIcon();
+ clearTypedText();
+ }
}
private void sendAdvanceMessage(long delay) {
@@ -1139,8 +1229,9 @@ public final class Launcher extends Activity
launcherInfo.hostView = null;
}
- void showOutOfSpaceMessage() {
- Toast.makeText(this, getString(R.string.out_of_space), Toast.LENGTH_SHORT).show();
+ void showOutOfSpaceMessage(boolean isHotseatLayout) {
+ int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
+ Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
}
public LauncherAppWidgetHost getAppWidgetHost() {
@@ -1408,6 +1499,7 @@ public final class Launcher extends Activity
mPendingAddInfo.screen = -1;
mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
+ mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
mPendingAddInfo.dropPos = null;
}
@@ -1419,9 +1511,9 @@ public final class Launcher extends Activity
addAppWidgetImpl(appWidgetId, null);
}
- void addAppWidgetImpl(int appWidgetId, PendingAddWidgetInfo info) {
- AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
-
+ void addAppWidgetImpl(final int appWidgetId, final PendingAddWidgetInfo info) {
+ final AppWidgetProviderInfo appWidget = info.info;
+ Runnable configurationActivity = null;
if (appWidget.configure != null) {
// Launch over to configure widget, if needed
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
@@ -1429,9 +1521,8 @@ public final class Launcher extends Activity
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
if (info != null) {
if (info.mimeType != null && !info.mimeType.isEmpty()) {
- intent.putExtra(
- InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE,
- info.mimeType);
+ intent.putExtra(InstallWidgetReceiver.
+ EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE, info.mimeType);
final String mimeType = info.mimeType;
final ClipData clipData = (ClipData) info.configurationData;
@@ -1442,8 +1533,8 @@ public final class Launcher extends Activity
final CharSequence stringData = item.getText();
final Uri uriData = item.getUri();
final Intent intentData = item.getIntent();
- final String key =
- InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA;
+ final String key = InstallWidgetReceiver.
+ EXTRA_APPWIDGET_CONFIGURATION_DATA;
if (uriData != null) {
intent.putExtra(key, uriData);
} else if (intentData != null) {
@@ -1456,14 +1547,13 @@ public final class Launcher extends Activity
}
}
}
-
startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
+ mWidgetBeingConfigured = info;
} else {
// Otherwise just add it
- completeAddAppWidget(appWidgetId, info.container, info.screen);
-
+ completeAddAppWidget(appWidgetId, info.container, info.screen, info.boundWidget, appWidget);
// Exit spring loaded mode if necessary after adding the widget
- exitSpringLoadedDragModeDelayed(true, false);
+ exitSpringLoadedDragModeDelayed(true, false, null);
}
}
@@ -1501,18 +1591,31 @@ public final class Launcher extends Activity
* @param position The location on the screen where it was dropped, optional
*/
void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, int screen,
- int[] cell, int[] loc) {
+ int[] cell, int[] span, int[] loc) {
resetAddInfo();
mPendingAddInfo.container = info.container = container;
mPendingAddInfo.screen = info.screen = screen;
mPendingAddInfo.dropPos = loc;
+ mPendingAddInfo.minSpanX = info.minSpanX;
+ mPendingAddInfo.minSpanY = info.minSpanY;
+
if (cell != null) {
mPendingAddInfo.cellX = cell[0];
mPendingAddInfo.cellY = cell[1];
}
+ if (span != null) {
+ mPendingAddInfo.spanX = span[0];
+ mPendingAddInfo.spanY = span[1];
+ }
- int appWidgetId = getAppWidgetHost().allocateAppWidgetId();
- AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, info.componentName);
+ AppWidgetHostView hostView = info.boundWidget;
+ int appWidgetId;
+ if (hostView != null) {
+ appWidgetId = hostView.getAppWidgetId();
+ } else {
+ appWidgetId = getAppWidgetHost().allocateAppWidgetId();
+ AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, info.componentName);
+ }
addAppWidgetImpl(appWidgetId, info);
}
@@ -1662,7 +1765,7 @@ public final class Launcher extends Activity
return;
}
- if (mWorkspace.isSwitchingState()) {
+ if (!mWorkspace.isFinishedSwitchingState()) {
return;
}
@@ -1743,6 +1846,8 @@ public final class Launcher extends Activity
public void onClickAppMarketButton(View v) {
if (mAppMarketIntent != null) {
startActivitySafely(mAppMarketIntent, "app market");
+ } else {
+ Log.e(TAG, "Invalid app market intent.");
}
}
@@ -1931,6 +2036,7 @@ public final class Launcher extends Activity
}
public boolean onLongClick(View v) {
+
if (mState != State.WORKSPACE) {
return false;
}
@@ -2098,7 +2204,7 @@ public final class Launcher extends Activity
if (mWorkspaceLoading) {
lockAllApps();
- mModel.startLoader(Launcher.this, false);
+ mModel.startLoader(false);
} else {
final FolderIcon folderIcon = (FolderIcon)
mWorkspace.getViewForTag(mFolderInfo);
@@ -2110,7 +2216,7 @@ public final class Launcher extends Activity
} else {
lockAllApps();
mWorkspaceLoading = true;
- mModel.startLoader(Launcher.this, false);
+ mModel.startLoader(false);
}
}
}
@@ -2129,6 +2235,10 @@ public final class Launcher extends Activity
return (mState == State.APPS_CUSTOMIZE);
}
+ public boolean isAllAppsButtonRank(int rank) {
+ return mHotseat.isAllAppsButtonRank(rank);
+ }
+
// AllAppsView.Watcher
public void zoomed(float zoom) {
if (zoom == 1.0f) {
@@ -2156,6 +2266,30 @@ public final class Launcher extends Activity
}
}
+ private void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
+ if (v instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) v).onLauncherTransitionStart(this, animated, toWorkspace);
+ }
+
+ // Update the workspace transition step as well
+ dispatchOnLauncherTransitionStep(v, 0f);
+ }
+
+ private void dispatchOnLauncherTransitionStep(View v, float t) {
+ if (v instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) v).onLauncherTransitionStep(this, t);
+ }
+ }
+
+ private void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
+ if (v instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) v).onLauncherTransitionEnd(this, animated, toWorkspace);
+ }
+
+ // Update the workspace transition step as well
+ dispatchOnLauncherTransitionStep(v, 1f);
+ }
+
/**
* Things to test when changing the following seven functions.
* - Home from workspace
@@ -2201,7 +2335,7 @@ public final class Launcher extends Activity
* Assumes that the view to show is anchored at either the very top or very bottom
* of the screen.
*/
- private void showAppsCustomizeHelper(boolean animated, final boolean springLoaded) {
+ private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded) {
if (mStateAnimation != null) {
mStateAnimation.cancel();
mStateAnimation = null;
@@ -2212,6 +2346,7 @@ public final class Launcher extends Activity
final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime);
final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime);
final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
+ final View fromView = mWorkspace;
final View toView = mAppsCustomizeTabHost;
final int startDelay =
res.getInteger(R.integer.config_workspaceAppsCustomizeAnimationStagger);
@@ -2219,32 +2354,40 @@ public final class Launcher extends Activity
setPivotsForZoom(toView, scale);
// Shrink workspaces away if going to AppsCustomize from workspace
- mWorkspace.changeState(Workspace.State.SMALL, animated);
+ Animator workspaceAnim =
+ mWorkspace.getChangeStateAnimation(Workspace.State.SMALL, animated);
if (animated) {
- final ValueAnimator scaleAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
- scaleAnim.setInterpolator(new Workspace.ZoomOutInterpolator());
- scaleAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
- public void onAnimationUpdate(float a, float b) {
- toView.setScaleX(a * scale + b * 1f);
- toView.setScaleY(a * scale + b * 1f);
- }
- });
+ toView.setScaleX(scale);
+ toView.setScaleY(scale);
+ final LauncherViewPropertyAnimator scaleAnim = new LauncherViewPropertyAnimator(toView);
+ scaleAnim.
+ scaleX(1f).scaleY(1f).
+ setDuration(duration).
+ setInterpolator(new Workspace.ZoomOutInterpolator());
toView.setVisibility(View.VISIBLE);
toView.setAlpha(0f);
- ValueAnimator alphaAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(fadeDuration);
+ final ObjectAnimator alphaAnim = ObjectAnimator
+ .ofFloat(toView, "alpha", 0f, 1f)
+ .setDuration(fadeDuration);
alphaAnim.setInterpolator(new DecelerateInterpolator(1.5f));
- alphaAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
- public void onAnimationUpdate(float a, float b) {
- // don't need to invalidate because we do so above
- toView.setAlpha(a * 0f + b * 1f);
+ alphaAnim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (Float) animation.getAnimatedValue();
+ dispatchOnLauncherTransitionStep(fromView, t);
+ dispatchOnLauncherTransitionStep(toView, t);
}
});
- alphaAnim.setStartDelay(startDelay);
- alphaAnim.start();
- scaleAnim.addListener(new AnimatorListenerAdapter() {
+ // toView should appear right at the end of the workspace shrink
+ // animation
+ mStateAnimation = new AnimatorSet();
+ mStateAnimation.play(scaleAnim).after(startDelay);
+ mStateAnimation.play(alphaAnim).after(startDelay);
+
+ mStateAnimation.addListener(new AnimatorListenerAdapter() {
boolean animationCancelled = false;
@Override
@@ -2258,15 +2401,8 @@ public final class Launcher extends Activity
}
@Override
public void onAnimationEnd(Animator animation) {
- // If we don't set the final scale values here, if this animation is cancelled
- // it will have the wrong scale value and subsequent cameraPan animations will
- // not fix that
- toView.setScaleX(1.0f);
- toView.setScaleY(1.0f);
- if (toView instanceof LauncherTransitionable) {
- ((LauncherTransitionable) toView).onLauncherTransitionEnd(instance,
- scaleAnim, false);
- }
+ dispatchOnLauncherTransitionEnd(fromView, animated, false);
+ dispatchOnLauncherTransitionEnd(toView, animated, false);
if (!springLoaded && !LauncherApplication.isScreenLarge()) {
// Hide the workspace scrollbar
@@ -2284,19 +2420,48 @@ public final class Launcher extends Activity
}
});
- // toView should appear right at the end of the workspace shrink animation
- mStateAnimation = new AnimatorSet();
- mStateAnimation.play(scaleAnim).after(startDelay);
+ if (workspaceAnim != null) {
+ mStateAnimation.play(workspaceAnim);
+ }
boolean delayAnim = false;
- if (toView instanceof LauncherTransitionable) {
- LauncherTransitionable lt = (LauncherTransitionable) toView;
- delayAnim = lt.onLauncherTransitionStart(instance, mStateAnimation, false);
+ final ViewTreeObserver observer;
+
+ dispatchOnLauncherTransitionStart(fromView, animated, false);
+ dispatchOnLauncherTransitionStart(toView, animated, false);
+
+ // If any of the objects being animated haven't been measured/laid out
+ // yet, delay the animation until we get a layout pass
+ if ((((LauncherTransitionable) toView).getContent().getMeasuredWidth() == 0) ||
+ (mWorkspace.getMeasuredWidth() == 0) ||
+ (toView.getMeasuredWidth() == 0)) {
+ observer = mWorkspace.getViewTreeObserver();
+ delayAnim = true;
+ } else {
+ observer = null;
}
- // if the anim is delayed, the LauncherTransitionable is responsible for starting it
- if (!delayAnim) {
- // TODO: q-- what if this anim is cancelled before being started? or started after
- // being cancelled?
+
+ if (delayAnim) {
+ final AnimatorSet stateAnimation = mStateAnimation;
+ final OnGlobalLayoutListener delayedStart = new OnGlobalLayoutListener() {
+ public void onGlobalLayout() {
+ mWorkspace.post(new Runnable() {
+ public void run() {
+ // Check that mStateAnimation hasn't changed while
+ // we waited for a layout pass
+ if (mStateAnimation == stateAnimation) {
+ // Need to update pivots for zoom if layout changed
+ setPivotsForZoom(toView, scale);
+ mStateAnimation.start();
+ }
+ }
+ });
+ observer.removeGlobalOnLayoutListener(this);
+ }
+ };
+ observer.addOnGlobalLayoutListener(delayedStart);
+ } else {
+ setPivotsForZoom(toView, scale);
mStateAnimation.start();
}
} else {
@@ -2306,16 +2471,16 @@ public final class Launcher extends Activity
toView.setScaleY(1.0f);
toView.setVisibility(View.VISIBLE);
toView.bringToFront();
- if (toView instanceof LauncherTransitionable) {
- ((LauncherTransitionable) toView).onLauncherTransitionStart(instance, null, false);
- ((LauncherTransitionable) toView).onLauncherTransitionEnd(instance, null, false);
-
- if (!springLoaded && !LauncherApplication.isScreenLarge()) {
- // Hide the workspace scrollbar
- mWorkspace.hideScrollingIndicator(true);
- hideDockDivider();
- }
+
+ if (!springLoaded && !LauncherApplication.isScreenLarge()) {
+ // Hide the workspace scrollbar
+ mWorkspace.hideScrollingIndicator(true);
+ hideDockDivider();
}
+ dispatchOnLauncherTransitionStart(fromView, animated, false);
+ dispatchOnLauncherTransitionEnd(fromView, animated, false);
+ dispatchOnLauncherTransitionStart(toView, animated, false);
+ dispatchOnLauncherTransitionEnd(toView, animated, false);
updateWallpaperVisibility(false);
}
}
@@ -2325,18 +2490,32 @@ public final class Launcher extends Activity
* This is the opposite of showAppsCustomizeHelper.
* @param animated If true, the transition will be animated.
*/
- private void hideAppsCustomizeHelper(boolean animated, final boolean springLoaded) {
+ private void hideAppsCustomizeHelper(State toState, final boolean animated,
+ final boolean springLoaded, final Runnable onCompleteRunnable) {
+
if (mStateAnimation != null) {
mStateAnimation.cancel();
mStateAnimation = null;
}
Resources res = getResources();
- final Launcher instance = this;
final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime);
+ final int fadeOutDuration =
+ res.getInteger(R.integer.config_appsCustomizeFadeOutTime);
final float scaleFactor = (float)
res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
final View fromView = mAppsCustomizeTabHost;
+ final View toView = mWorkspace;
+ Animator workspaceAnim = null;
+
+ if (toState == State.WORKSPACE) {
+ int stagger = res.getInteger(R.integer.config_appsCustomizeWorkspaceAnimationStagger);
+ workspaceAnim = mWorkspace.getChangeStateAnimation(
+ Workspace.State.NORMAL, animated, stagger);
+ } else if (toState == State.APPS_CUSTOMIZE_SPRING_LOADED) {
+ workspaceAnim = mWorkspace.getChangeStateAnimation(
+ Workspace.State.SPRING_LOADED, animated);
+ }
setPivotsForZoom(fromView, scaleFactor);
updateWallpaperVisibility(true);
@@ -2345,48 +2524,56 @@ public final class Launcher extends Activity
final float oldScaleX = fromView.getScaleX();
final float oldScaleY = fromView.getScaleY();
- ValueAnimator scaleAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
- scaleAnim.setInterpolator(new Workspace.ZoomInInterpolator());
- scaleAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
- public void onAnimationUpdate(float a, float b) {
- fromView.setScaleX(a * oldScaleX + b * scaleFactor);
- fromView.setScaleY(a * oldScaleY + b * scaleFactor);
- }
- });
- final ValueAnimator alphaAnim = ValueAnimator.ofFloat(0f, 1f);
- alphaAnim.setDuration(res.getInteger(R.integer.config_appsCustomizeFadeOutTime));
+ final LauncherViewPropertyAnimator scaleAnim =
+ new LauncherViewPropertyAnimator(fromView);
+ scaleAnim.
+ scaleX(scaleFactor).scaleY(scaleFactor).
+ setDuration(duration).
+ setInterpolator(new Workspace.ZoomInInterpolator());
+
+ final ObjectAnimator alphaAnim = ObjectAnimator
+ .ofFloat(fromView, "alpha", 1f, 0f)
+ .setDuration(fadeOutDuration);
alphaAnim.setInterpolator(new AccelerateDecelerateInterpolator());
- alphaAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
- public void onAnimationUpdate(float a, float b) {
- fromView.setAlpha(a * 1f + b * 0f);
+ alphaAnim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = 1f - (Float) animation.getAnimatedValue();
+ dispatchOnLauncherTransitionStep(fromView, t);
+ dispatchOnLauncherTransitionStep(toView, t);
}
});
- if (fromView instanceof LauncherTransitionable) {
- ((LauncherTransitionable) fromView).onLauncherTransitionStart(instance, alphaAnim,
- true);
- }
- alphaAnim.addListener(new AnimatorListenerAdapter() {
+
+ mStateAnimation = new AnimatorSet();
+
+ dispatchOnLauncherTransitionStart(fromView, animated, true);
+ dispatchOnLauncherTransitionStart(toView, animated, true);
+
+ mStateAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
updateWallpaperVisibility(true);
fromView.setVisibility(View.GONE);
- if (fromView instanceof LauncherTransitionable) {
- ((LauncherTransitionable) fromView).onLauncherTransitionEnd(instance,
- alphaAnim, true);
- }
+ dispatchOnLauncherTransitionEnd(fromView, animated, true);
+ dispatchOnLauncherTransitionEnd(toView, animated, true);
mWorkspace.hideScrollingIndicator(false);
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
}
});
- mStateAnimation = new AnimatorSet();
mStateAnimation.playTogether(scaleAnim, alphaAnim);
+ if (workspaceAnim != null) {
+ mStateAnimation.play(workspaceAnim);
+ }
mStateAnimation.start();
} else {
fromView.setVisibility(View.GONE);
- if (fromView instanceof LauncherTransitionable) {
- ((LauncherTransitionable) fromView).onLauncherTransitionStart(instance, null, true);
- ((LauncherTransitionable) fromView).onLauncherTransitionEnd(instance, null, true);
- }
+ dispatchOnLauncherTransitionStart(fromView, animated, true);
+ dispatchOnLauncherTransitionEnd(fromView, animated, true);
+ dispatchOnLauncherTransitionStart(toView, animated, true);
+ dispatchOnLauncherTransitionEnd(toView, animated, true);
mWorkspace.hideScrollingIndicator(false);
}
}
@@ -2394,19 +2581,19 @@ public final class Launcher extends Activity
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
- if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
+ if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
mAppsCustomizeTabHost.onTrimMemory();
}
}
void showWorkspace(boolean animated) {
- Resources res = getResources();
- int stagger = res.getInteger(R.integer.config_appsCustomizeWorkspaceAnimationStagger);
+ showWorkspace(animated, null);
+ }
- mWorkspace.changeState(Workspace.State.NORMAL, animated, stagger);
+ void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
if (mState != State.WORKSPACE) {
mWorkspace.setVisibility(View.VISIBLE);
- hideAppsCustomizeHelper(animated, false);
+ hideAppsCustomizeHelper(State.WORKSPACE, animated, false, onCompleteRunnable);
// Show the search bar and hotseat
mSearchDropTargetBar.showSearchBar(animated);
@@ -2455,14 +2642,14 @@ public final class Launcher extends Activity
void enterSpringLoadedDragMode() {
if (mState == State.APPS_CUSTOMIZE) {
- mWorkspace.changeState(Workspace.State.SPRING_LOADED);
- hideAppsCustomizeHelper(true, true);
+ hideAppsCustomizeHelper(State.APPS_CUSTOMIZE_SPRING_LOADED, true, true, null);
hideDockDivider();
mState = State.APPS_CUSTOMIZE_SPRING_LOADED;
}
}
- void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay) {
+ void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay,
+ final Runnable onCompleteRunnable) {
if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return;
mHandler.postDelayed(new Runnable() {
@@ -2474,7 +2661,7 @@ public final class Launcher extends Activity
// clean up our state transition functions
mAppsCustomizeTabHost.setVisibility(View.GONE);
mSearchDropTargetBar.showSearchBar(true);
- showWorkspace(true);
+ showWorkspace(true, onCompleteRunnable);
} else {
exitSpringLoadedDragMode();
}
@@ -2539,8 +2726,10 @@ public final class Launcher extends Activity
void showHotseat(boolean animated) {
if (!LauncherApplication.isScreenLarge()) {
if (animated) {
- int duration = mSearchDropTargetBar.getTransitionInDuration();
- mHotseat.animate().alpha(1f).setDuration(duration);
+ if (mHotseat.getAlpha() != 1f) {
+ int duration = mSearchDropTargetBar.getTransitionInDuration();
+ mHotseat.animate().alpha(1f).setDuration(duration);
+ }
} else {
mHotseat.setAlpha(1f);
}
@@ -2553,8 +2742,10 @@ public final class Launcher extends Activity
void hideHotseat(boolean animated) {
if (!LauncherApplication.isScreenLarge()) {
if (animated) {
- int duration = mSearchDropTargetBar.getTransitionOutDuration();
- mHotseat.animate().alpha(0f).setDuration(duration);
+ if (mHotseat.getAlpha() != 0f) {
+ int duration = mSearchDropTargetBar.getTransitionOutDuration();
+ mHotseat.animate().alpha(0f).setDuration(duration);
+ }
} else {
mHotseat.setAlpha(0f);
}
@@ -2567,7 +2758,7 @@ public final class Launcher extends Activity
*/
void addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout) {
if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) {
- showOutOfSpaceMessage();
+ showOutOfSpaceMessage(isHotseatLayout(layout));
}
}
@@ -2905,7 +3096,6 @@ public final class Launcher extends Activity
}
}
-
/**
* Refreshes the shortcuts shown on the workspace.
*
@@ -2914,6 +3104,8 @@ public final class Launcher extends Activity
public void startBinding() {
final Workspace workspace = mWorkspace;
+ mNewShortcutAnimatePage = -1;
+ mNewShortcutAnimateViews.clear();
mWorkspace.clearDropTargets();
int count = workspace.getChildCount();
for (int i = 0; i < count; i++) {
@@ -2935,8 +3127,12 @@ public final class Launcher extends Activity
public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end) {
setLoadOnResume();
- final Workspace workspace = mWorkspace;
- for (int i=start; i<end; i++) {
+ // Get the list of added shortcuts and intersect them with the set of shortcuts here
+ Set<String> newApps = new HashSet<String>();
+ newApps = mSharedPrefs.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, newApps);
+
+ Workspace workspace = mWorkspace;
+ for (int i = start; i < end; i++) {
final ItemInfo item = shortcuts.get(i);
// Short circuit if we are loading dock items for a configuration which has no dock
@@ -2948,9 +3144,23 @@ public final class Launcher extends Activity
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
- View shortcut = createShortcut((ShortcutInfo)item);
+ ShortcutInfo info = (ShortcutInfo) item;
+ String uri = info.intent.toUri(0).toString();
+ View shortcut = createShortcut(info);
workspace.addInScreen(shortcut, item.container, item.screen, item.cellX,
item.cellY, 1, 1, false);
+ if (newApps.contains(uri)) {
+ newApps.remove(uri);
+
+ // Prepare the view to be animated up
+ shortcut.setAlpha(0f);
+ shortcut.setScaleX(0f);
+ shortcut.setScaleY(0f);
+ mNewShortcutAnimatePage = item.screen;
+ if (!mNewShortcutAnimateViews.contains(shortcut)) {
+ mNewShortcutAnimateViews.add(shortcut);
+ }
+ }
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
@@ -2961,6 +3171,7 @@ public final class Launcher extends Activity
break;
}
}
+
workspace.requestLayout();
}
@@ -3031,8 +3242,6 @@ public final class Launcher extends Activity
mSavedInstanceState = null;
}
- mWorkspaceLoading = false;
-
// If we received the result of any pending adds while the loader was running (e.g. the
// widget configuration forced an orientation change), process them now.
for (int i = 0; i < sPendingAddList.size(); i++) {
@@ -3044,7 +3253,72 @@ public final class Launcher extends Activity
// package changes in bindSearchablesChanged()
updateAppMarketIcon();
- mWorkspace.post(mBuildLayersRunnable);
+ // Animate up any icons as necessary
+ if (mVisible || mWorkspaceLoading) {
+ Runnable newAppsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ runNewAppsAnimation();
+ }
+ };
+ if (mNewShortcutAnimatePage > -1 &&
+ mNewShortcutAnimatePage != mWorkspace.getCurrentPage()) {
+ mWorkspace.snapToPage(mNewShortcutAnimatePage, newAppsRunnable);
+ } else {
+ newAppsRunnable.run();
+ }
+ }
+
+ mWorkspaceLoading = false;
+ }
+
+ /**
+ * Runs a new animation that scales up icons that were added while Launcher was in the
+ * background.
+ */
+ private void runNewAppsAnimation() {
+ AnimatorSet anim = new AnimatorSet();
+ Collection<Animator> bounceAnims = new ArrayList<Animator>();
+ Collections.sort(mNewShortcutAnimateViews, new Comparator<View>() {
+ @Override
+ public int compare(View a, View b) {
+ CellLayout.LayoutParams alp = (CellLayout.LayoutParams) a.getLayoutParams();
+ CellLayout.LayoutParams blp = (CellLayout.LayoutParams) b.getLayoutParams();
+ int cellCountX = LauncherModel.getCellCountX();
+ return (alp.cellY * cellCountX + alp.cellX) - (blp.cellY * cellCountX + blp.cellX);
+ }
+ });
+ for (int i = 0; i < mNewShortcutAnimateViews.size(); ++i) {
+ View v = mNewShortcutAnimateViews.get(i);
+ ValueAnimator bounceAnim = ObjectAnimator.ofPropertyValuesHolder(v,
+ PropertyValuesHolder.ofFloat("alpha", 1f),
+ PropertyValuesHolder.ofFloat("scaleX", 1f),
+ PropertyValuesHolder.ofFloat("scaleY", 1f));
+ bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
+ bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
+ bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
+ bounceAnims.add(bounceAnim);
+ }
+ anim.playTogether(bounceAnims);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mWorkspace.postDelayed(mBuildLayersRunnable, 500);
+ }
+ });
+ anim.start();
+
+ // Clean up
+ mNewShortcutAnimatePage = -1;
+ mNewShortcutAnimateViews.clear();
+ new Thread("clearNewAppsThread") {
+ public void run() {
+ mSharedPrefs.edit()
+ .putInt(InstallShortcutReceiver.NEW_APPS_PAGE_KEY, -1)
+ .putStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, null)
+ .commit();
+ }
+ }.start();
}
@Override
@@ -3187,7 +3461,6 @@ public final class Launcher extends Activity
}
/* Cling related */
- private static final String PREFS_KEY = "com.android.launcher2.prefs";
private boolean isClingsEnabled() {
// disable clings when running in a test harness
if(ActivityManager.isRunningInTestHarness()) return false;
@@ -3223,11 +3496,14 @@ public final class Launcher extends Activity
public void onAnimationEnd(Animator animation) {
cling.setVisibility(View.GONE);
cling.cleanup();
- SharedPreferences prefs =
- getSharedPreferences("com.android.launcher2.prefs", Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(flag, true);
- editor.commit();
+ // We should update the shared preferences on a background thread
+ new Thread("dismissClingThread") {
+ public void run() {
+ SharedPreferences.Editor editor = mSharedPrefs.edit();
+ editor.putBoolean(flag, true);
+ editor.commit();
+ }
+ }.start();
};
});
anim.start();
@@ -3247,9 +3523,8 @@ public final class Launcher extends Activity
}
public void showFirstRunWorkspaceCling() {
// Enable the clings only if they have not been dismissed before
- SharedPreferences prefs =
- getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE);
- if (isClingsEnabled() && !prefs.getBoolean(Cling.WORKSPACE_CLING_DISMISSED_KEY, false)) {
+ if (isClingsEnabled() &&
+ !mSharedPrefs.getBoolean(Cling.WORKSPACE_CLING_DISMISSED_KEY, false)) {
initCling(R.id.workspace_cling, null, false, 0);
} else {
removeCling(R.id.workspace_cling);
@@ -3257,9 +3532,8 @@ public final class Launcher extends Activity
}
public void showFirstRunAllAppsCling(int[] position) {
// Enable the clings only if they have not been dismissed before
- SharedPreferences prefs =
- getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE);
- if (isClingsEnabled() && !prefs.getBoolean(Cling.ALLAPPS_CLING_DISMISSED_KEY, false)) {
+ if (isClingsEnabled() &&
+ !mSharedPrefs.getBoolean(Cling.ALLAPPS_CLING_DISMISSED_KEY, false)) {
initCling(R.id.all_apps_cling, position, true, 0);
} else {
removeCling(R.id.all_apps_cling);
@@ -3267,15 +3541,13 @@ public final class Launcher extends Activity
}
public Cling showFirstRunFoldersCling() {
// Enable the clings only if they have not been dismissed before
- SharedPreferences prefs =
- getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE);
- Cling cling = null;
- if (isClingsEnabled() && !prefs.getBoolean(Cling.FOLDER_CLING_DISMISSED_KEY, false)) {
- cling = initCling(R.id.folder_cling, null, true, 0);
+ if (isClingsEnabled() &&
+ !mSharedPrefs.getBoolean(Cling.FOLDER_CLING_DISMISSED_KEY, false)) {
+ return initCling(R.id.folder_cling, null, true, 0);
} else {
removeCling(R.id.folder_cling);
+ return null;
}
- return cling;
}
public boolean isFolderClingVisible() {
Cling cling = (Cling) findViewById(R.id.folder_cling);
@@ -3328,7 +3600,8 @@ public final class Launcher extends Activity
}
interface LauncherTransitionable {
- // return true if the callee will take care of start the animation by itself
- boolean onLauncherTransitionStart(Launcher l, Animator animation, boolean toWorkspace);
- void onLauncherTransitionEnd(Launcher l, Animator animation, boolean toWorkspace);
+ View getContent();
+ void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
+ void onLauncherTransitionStep(Launcher l, float t);
+ void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
}
diff --git a/src/com/android/launcher2/LauncherAppWidgetHostView.java b/src/com/android/launcher2/LauncherAppWidgetHostView.java
index 0c3bdcaf8..d73dd3008 100644
--- a/src/com/android/launcher2/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher2/LauncherAppWidgetHostView.java
@@ -30,12 +30,12 @@ import com.android.launcher.R;
* {@inheritDoc}
*/
public class LauncherAppWidgetHostView extends AppWidgetHostView {
- private boolean mHasPerformedLongPress;
- private CheckForLongPress mPendingCheckForLongPress;
+ private CheckLongPressHelper mLongPressHelper;
private LayoutInflater mInflater;
public LauncherAppWidgetHostView(Context context) {
super(context);
+ mLongPressHelper = new CheckLongPressHelper(this);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@@ -46,8 +46,8 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView {
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Consume any touch events for ourselves after longpress is triggered
- if (mHasPerformedLongPress) {
- mHasPerformedLongPress = false;
+ if (mLongPressHelper.hasPerformedLongPress()) {
+ mLongPressHelper.cancelLongPress();
return true;
}
@@ -55,16 +55,13 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView {
// users can always pick up this widget
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
- postCheckForLongClick();
+ mLongPressHelper.postCheckForLongPress();
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- mHasPerformedLongPress = false;
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ mLongPressHelper.cancelLongPress();
break;
}
@@ -72,42 +69,11 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView {
return false;
}
- class CheckForLongPress implements Runnable {
- private int mOriginalWindowAttachCount;
-
- public void run() {
- if ((mParent != null) && hasWindowFocus()
- && mOriginalWindowAttachCount == getWindowAttachCount()
- && !mHasPerformedLongPress) {
- if (performLongClick()) {
- mHasPerformedLongPress = true;
- }
- }
- }
-
- public void rememberWindowAttachCount() {
- mOriginalWindowAttachCount = getWindowAttachCount();
- }
- }
-
- private void postCheckForLongClick() {
- mHasPerformedLongPress = false;
-
- if (mPendingCheckForLongPress == null) {
- mPendingCheckForLongPress = new CheckForLongPress();
- }
- mPendingCheckForLongPress.rememberWindowAttachCount();
- postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
- }
-
@Override
public void cancelLongPress() {
super.cancelLongPress();
- mHasPerformedLongPress = false;
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ mLongPressHelper.cancelLongPress();
}
@Override
diff --git a/src/com/android/launcher2/LauncherApplication.java b/src/com/android/launcher2/LauncherApplication.java
index 9936ca682..ef1eb5fcd 100644
--- a/src/com/android/launcher2/LauncherApplication.java
+++ b/src/com/android/launcher2/LauncherApplication.java
@@ -25,6 +25,9 @@ import android.content.IntentFilter;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.os.Handler;
+import android.view.MotionEvent;
+
+import com.android.launcher.R;
import java.lang.ref.WeakReference;
@@ -33,6 +36,8 @@ public class LauncherApplication extends Application {
public IconCache mIconCache;
private static boolean sIsScreenLarge;
private static float sScreenDensity;
+ private static int sLongPressTimeout = 300;
+ private static final String sSharedPreferencesKey = "com.android.launcher2.prefs";
WeakReference<LauncherProvider> mLauncherProvider;
@Override
@@ -40,10 +45,7 @@ public class LauncherApplication extends Application {
super.onCreate();
// set sIsScreenXLarge and sScreenDensity *before* creating icon cache
- final int screenSize = getResources().getConfiguration().screenLayout &
- Configuration.SCREENLAYOUT_SIZE_MASK;
- sIsScreenLarge = screenSize == Configuration.SCREENLAYOUT_SIZE_LARGE ||
- screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ sIsScreenLarge = getResources().getBoolean(R.bool.is_large_screen);
sScreenDensity = getResources().getDisplayMetrics().density;
mIconCache = new IconCache(this);
@@ -93,7 +95,10 @@ public class LauncherApplication extends Application {
private final ContentObserver mFavoritesObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
- mModel.startLoader(LauncherApplication.this, false);
+ // If the database has ever changed, then we really need to force a reload of the
+ // workspace on the next load
+ mModel.resetLoadedState(false, true);
+ mModel.startLoaderFromBackground();
}
};
@@ -118,6 +123,10 @@ public class LauncherApplication extends Application {
return mLauncherProvider.get();
}
+ public static String getSharedPreferencesKey() {
+ return sSharedPreferencesKey;
+ }
+
public static boolean isScreenLarge() {
return sIsScreenLarge;
}
@@ -130,4 +139,8 @@ public class LauncherApplication extends Application {
public static float getScreenDensity() {
return sScreenDensity;
}
+
+ public static int getLongPressTimeout() {
+ return sLongPressTimeout;
+ }
}
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index 7da55db6d..32c77c7c1 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -138,6 +138,7 @@ public class LauncherModel extends BroadcastReceiver {
public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent);
public void bindPackagesUpdated();
public boolean isAllAppsVisible();
+ public boolean isAllAppsButtonRank(int rank);
public void bindSearchablesChanged();
}
@@ -648,17 +649,22 @@ public class LauncherModel extends BroadcastReceiver {
}
private void forceReload() {
+ resetLoadedState(true, true);
+
+ // Do this here because if the launcher activity is running it will be restarted.
+ // If it's not running startLoaderFromBackground will merely tell it that it needs
+ // to reload.
+ startLoaderFromBackground();
+ }
+
+ public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
synchronized (mLock) {
// Stop any existing loaders first, so they don't set mAllAppsLoaded or
// mWorkspaceLoaded to true later
stopLoaderLocked();
- mAllAppsLoaded = false;
- mWorkspaceLoaded = false;
+ if (resetAllAppsLoaded) mAllAppsLoaded = false;
+ if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
}
- // Do this here because if the launcher activity is running it will be restarted.
- // If it's not running startLoaderFromBackground will merely tell it that it needs
- // to reload.
- startLoaderFromBackground();
}
/**
@@ -679,7 +685,7 @@ public class LauncherModel extends BroadcastReceiver {
}
}
if (runLoader) {
- startLoader(mApp, false);
+ startLoader(false);
}
}
@@ -697,7 +703,7 @@ public class LauncherModel extends BroadcastReceiver {
return isLaunching;
}
- public void startLoader(Context context, boolean isLaunching) {
+ public void startLoader(boolean isLaunching) {
synchronized (mLock) {
if (DEBUG_LOADERS) {
Log.d(TAG, "startLoader isLaunching=" + isLaunching);
@@ -708,7 +714,7 @@ public class LauncherModel extends BroadcastReceiver {
// If there is already one running, tell it to stop.
// also, don't downgrade isLaunching if we're already running
isLaunching = isLaunching || stopLoaderLocked();
- mLoaderTask = new LoaderTask(context, isLaunching);
+ mLoaderTask = new LoaderTask(mApp, isLaunching);
sWorkerThread.setPriority(Thread.NORM_PRIORITY);
sWorker.post(mLoaderTask);
}
@@ -920,7 +926,7 @@ public class LauncherModel extends BroadcastReceiver {
int containerIndex = item.screen;
if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
// Return early if we detect that an item is under the hotseat button
- if (Hotseat.isAllAppsButtonRank(item.screen)) {
+ if (mCallbacks == null || mCallbacks.get().isAllAppsButtonRank(item.screen)) {
return false;
}
@@ -1348,6 +1354,7 @@ public class LauncherModel extends BroadcastReceiver {
}
// shallow copy
+ @SuppressWarnings("unchecked")
final ArrayList<ApplicationInfo> list
= (ArrayList<ApplicationInfo>) mAllAppsList.data.clone();
mHandler.post(new Runnable() {
@@ -1651,7 +1658,26 @@ public class LauncherModel extends BroadcastReceiver {
// but don't worry about that. All we're doing with usingFallbackIcon is
// to avoid saving lots of copies of that in the database, and most apps
// have icons anyway.
- final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0);
+
+ // Attempt to use queryIntentActivities to get the ResolveInfo (with IntentFilter info) and
+ // if that fails, or is ambiguious, fallback to the standard way of getting the resolve info
+ // via resolveActivity().
+ ResolveInfo resolveInfo = null;
+ ComponentName oldComponent = intent.getComponent();
+ Intent newIntent = new Intent(intent.getAction(), null);
+ newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ newIntent.setPackage(oldComponent.getPackageName());
+ List<ResolveInfo> infos = manager.queryIntentActivities(newIntent, 0);
+ for (ResolveInfo i : infos) {
+ ComponentName cn = new ComponentName(i.activityInfo.packageName,
+ i.activityInfo.name);
+ if (cn.equals(oldComponent)) {
+ resolveInfo = i;
+ }
+ }
+ if (resolveInfo == null) {
+ resolveInfo = manager.resolveActivity(intent, 0);
+ }
if (resolveInfo != null) {
icon = mIconCache.getIcon(componentName, resolveInfo, labelCache);
}
diff --git a/src/com/android/launcher2/LauncherProvider.java b/src/com/android/launcher2/LauncherProvider.java
index a01cc479b..de9a9b2de 100644
--- a/src/com/android/launcher2/LauncherProvider.java
+++ b/src/com/android/launcher2/LauncherProvider.java
@@ -706,6 +706,8 @@ public class LauncherProvider extends ContentProvider {
ContentValues values = new ContentValues();
PackageManager packageManager = mContext.getPackageManager();
+ int allAppsButtonRank =
+ mContext.getResources().getInteger(R.integer.hotseat_all_apps_index);
int i = 0;
try {
XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
@@ -739,8 +741,8 @@ public class LauncherProvider extends ContentProvider {
// If we are adding to the hotseat, the screen is used as the position in the
// hotseat. This screen can't be at position 0 because AllApps is in the
// zeroth position.
- if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
- Hotseat.isAllAppsButtonRank(Integer.valueOf(screen))) {
+ if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
+ && Integer.valueOf(screen) == allAppsButtonRank) {
throw new RuntimeException("Invalid screen position for hotseat item");
}
diff --git a/src/com/android/launcher2/LauncherViewPropertyAnimator.java b/src/com/android/launcher2/LauncherViewPropertyAnimator.java
new file mode 100644
index 000000000..88b4cb4b8
--- /dev/null
+++ b/src/com/android/launcher2/LauncherViewPropertyAnimator.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2012 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.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.TimeInterpolator;
+import android.view.ViewPropertyAnimator;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+
+public class LauncherViewPropertyAnimator extends Animator implements AnimatorListener {
+ enum Properties {
+ TRANSLATION_X,
+ TRANSLATION_Y,
+ SCALE_X,
+ SCALE_Y,
+ ROTATION_Y,
+ ALPHA,
+ START_DELAY,
+ DURATION,
+ INTERPOLATOR
+ }
+ EnumSet<Properties> mPropertiesToSet = EnumSet.noneOf(Properties.class);
+ ViewPropertyAnimator mViewPropertyAnimator;
+ View mTarget;
+
+ float mTranslationX;
+ float mTranslationY;
+ float mScaleX;
+ float mScaleY;
+ float mRotationY;
+ float mAlpha;
+ long mStartDelay;
+ long mDuration;
+ TimeInterpolator mInterpolator;
+ ArrayList<Animator.AnimatorListener> mListeners;
+ boolean mRunning = false;
+
+ public LauncherViewPropertyAnimator(View target) {
+ mTarget = target;
+ mListeners = new ArrayList<Animator.AnimatorListener>();
+ }
+
+ @Override
+ public void addListener(Animator.AnimatorListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void cancel() {
+ if (mViewPropertyAnimator != null) {
+ mViewPropertyAnimator.cancel();
+ }
+ }
+
+ @Override
+ public Animator clone() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void end() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public ArrayList<Animator.AnimatorListener> getListeners() {
+ return mListeners;
+ }
+
+ @Override
+ public long getStartDelay() {
+ return mStartDelay;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ Animator.AnimatorListener listener = mListeners.get(i);
+ listener.onAnimationCancel(this);
+ }
+ mRunning = false;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ Animator.AnimatorListener listener = mListeners.get(i);
+ listener.onAnimationEnd(this);
+ }
+ mRunning = false;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ Animator.AnimatorListener listener = mListeners.get(i);
+ listener.onAnimationRepeat(this);
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ Animator.AnimatorListener listener = mListeners.get(i);
+ listener.onAnimationStart(this);
+ }
+ mRunning = true;
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ @Override
+ public boolean isStarted() {
+ return mViewPropertyAnimator != null;
+ }
+
+ @Override
+ public void removeAllListeners() {
+ mListeners.clear();
+ }
+
+ @Override
+ public void removeListener(Animator.AnimatorListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public Animator setDuration(long duration) {
+ mPropertiesToSet.add(Properties.DURATION);
+ mDuration = duration;
+ return this;
+ }
+
+ @Override
+ public void setInterpolator(TimeInterpolator value) {
+ mPropertiesToSet.add(Properties.INTERPOLATOR);
+ mInterpolator = value;
+ }
+
+ @Override
+ public void setStartDelay(long startDelay) {
+ mPropertiesToSet.add(Properties.START_DELAY);
+ mStartDelay = startDelay;
+ }
+
+ @Override
+ public void setTarget(Object target) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void setupEndValues() {
+
+ }
+
+ @Override
+ public void setupStartValues() {
+ }
+
+ @Override
+ public void start() {
+ mViewPropertyAnimator = mTarget.animate();
+ if (mPropertiesToSet.contains(Properties.TRANSLATION_X)) {
+ mViewPropertyAnimator.translationX(mTranslationX);
+ }
+ if (mPropertiesToSet.contains(Properties.TRANSLATION_Y)) {
+ mViewPropertyAnimator.translationY(mTranslationY);
+ }
+ if (mPropertiesToSet.contains(Properties.SCALE_X)) {
+ mViewPropertyAnimator.scaleX(mScaleX);
+ }
+ if (mPropertiesToSet.contains(Properties.ROTATION_Y)) {
+ mViewPropertyAnimator.rotationY(mRotationY);
+ }
+ if (mPropertiesToSet.contains(Properties.SCALE_Y)) {
+ mViewPropertyAnimator.scaleY(mScaleY);
+ }
+ if (mPropertiesToSet.contains(Properties.ALPHA)) {
+ mViewPropertyAnimator.alpha(mAlpha);
+ }
+ if (mPropertiesToSet.contains(Properties.START_DELAY)) {
+ mViewPropertyAnimator.setStartDelay(mStartDelay);
+ }
+ if (mPropertiesToSet.contains(Properties.DURATION)) {
+ mViewPropertyAnimator.setDuration(mDuration);
+ }
+ if (mPropertiesToSet.contains(Properties.INTERPOLATOR)) {
+ mViewPropertyAnimator.setInterpolator(mInterpolator);
+ }
+ mViewPropertyAnimator.setListener(this);
+ mViewPropertyAnimator.start();
+ }
+
+ public LauncherViewPropertyAnimator translationX(float value) {
+ mPropertiesToSet.add(Properties.TRANSLATION_X);
+ mTranslationX = value;
+ return this;
+ }
+
+ public LauncherViewPropertyAnimator translationY(float value) {
+ mPropertiesToSet.add(Properties.TRANSLATION_Y);
+ mTranslationY = value;
+ return this;
+ }
+
+ public LauncherViewPropertyAnimator scaleX(float value) {
+ mPropertiesToSet.add(Properties.SCALE_X);
+ mScaleX = value;
+ return this;
+ }
+
+ public LauncherViewPropertyAnimator scaleY(float value) {
+ mPropertiesToSet.add(Properties.SCALE_Y);
+ mScaleY = value;
+ return this;
+ }
+
+ public LauncherViewPropertyAnimator rotationY(float value) {
+ mPropertiesToSet.add(Properties.ROTATION_Y);
+ mRotationY = value;
+ return this;
+ }
+
+ public LauncherViewPropertyAnimator alpha(float value) {
+ mPropertiesToSet.add(Properties.ALPHA);
+ mAlpha = value;
+ return this;
+ }
+}
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
index 3f5652e1d..8a2a5a0ac 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -62,19 +62,26 @@ public abstract class PagedView extends ViewGroup {
// the min drag distance for a fling to register, to prevent random page shifts
private static final int MIN_LENGTH_FOR_FLING = 25;
- private static final int PAGE_SNAP_ANIMATION_DURATION = 550;
+ protected static final int PAGE_SNAP_ANIMATION_DURATION = 550;
+ protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
protected static final float NANOTIME_DIV = 1000000000.0f;
private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
private static final float OVERSCROLL_DAMP_FACTOR = 0.14f;
- private static final int MINIMUM_SNAP_VELOCITY = 2200;
- private static final int MIN_FLING_VELOCITY = 250;
+
private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
// The page is moved more than halfway, automatically move to the next page on touch up.
private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
- // the velocity at which a fling gesture will cause us to snap to the next page
- protected int mSnapVelocity = 500;
+ // The following constants need to be scaled based on density. The scaled versions will be
+ // assigned to the corresponding member variables below.
+ private static final int FLING_THRESHOLD_VELOCITY = 500;
+ private static final int MIN_SNAP_VELOCITY = 1500;
+ private static final int MIN_FLING_VELOCITY = 250;
+
+ protected int mFlingThresholdVelocity;
+ protected int mMinFlingVelocity;
+ protected int mMinSnapVelocity;
protected float mDensity;
protected float mSmoothingTime;
@@ -145,15 +152,6 @@ public abstract class PagedView extends ViewGroup {
protected ArrayList<Boolean> mDirtyPageContent;
- // choice modes
- protected static final int CHOICE_MODE_NONE = 0;
- protected static final int CHOICE_MODE_SINGLE = 1;
- // Multiple selection mode is not supported by all Launcher actions atm
- protected static final int CHOICE_MODE_MULTIPLE = 2;
-
- protected int mChoiceMode;
- private ActionMode mActionMode;
-
// If true, syncPages and syncPageItems will be called to refresh pages
protected boolean mContentIsRefreshable = true;
@@ -175,10 +173,12 @@ public abstract class PagedView extends ViewGroup {
// Scrolling indicator
private ValueAnimator mScrollIndicatorAnimator;
- private ImageView mScrollIndicator;
+ private View mScrollIndicator;
private int mScrollIndicatorPaddingLeft;
private int mScrollIndicatorPaddingRight;
private boolean mHasScrollIndicator = true;
+ private boolean mShouldShowScrollIndicator = false;
+ private boolean mShouldShowScrollIndicatorImmediately = false;
protected static final int sScrollIndicatorFadeInDuration = 150;
protected static final int sScrollIndicatorFadeOutDuration = 650;
protected static final int sScrollIndicatorFlashDuration = 650;
@@ -200,7 +200,6 @@ public abstract class PagedView extends ViewGroup {
public PagedView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mChoiceMode = CHOICE_MODE_NONE;
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.PagedView, defStyle, 0);
@@ -242,6 +241,10 @@ public abstract class PagedView extends ViewGroup {
mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mDensity = getResources().getDisplayMetrics().density;
+
+ mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
+ mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
+ mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
}
public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
@@ -292,6 +295,7 @@ public abstract class PagedView extends ViewGroup {
int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage);
scrollTo(newX, 0);
mScroller.setFinalX(newX);
+ mScroller.forceFinished(true);
}
/**
@@ -340,12 +344,10 @@ public abstract class PagedView extends ViewGroup {
// a method that subclasses can override to add behavior
protected void onPageBeginMoving() {
- showScrollingIndicator(false);
}
// a method that subclasses can override to add behavior
protected void onPageEndMoving() {
- hideScrollingIndicator(false);
}
/**
@@ -579,7 +581,14 @@ public abstract class PagedView extends ViewGroup {
// Calculate the variable page spacing if necessary
if (mPageSpacing < 0) {
- setPageSpacing(((right - left) - getChildAt(0).getMeasuredWidth()) / 2);
+ // The gap between pages in the PagedView should be equal to the gap from the page
+ // to the edge of the screen (so it is not visible in the current screen). To
+ // account for unequal padding on each side of the paged view, we take the maximum
+ // of the left/right gap and use that as the gap between each page.
+ int offset = getRelativeChildOffset(0);
+ int spacing = Math.max(offset, (right - left) - offset -
+ getChildAt(0).getMeasuredWidth());
+ setPageSpacing(spacing);
}
}
@@ -602,9 +611,7 @@ public abstract class PagedView extends ViewGroup {
if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
setHorizontalScrollBarEnabled(false);
- int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage);
- scrollTo(newX, 0);
- mScroller.setFinalX(newX);
+ updateCurrentPageScroll();
setHorizontalScrollBarEnabled(true);
mFirstLayout = false;
}
@@ -624,8 +631,7 @@ public abstract class PagedView extends ViewGroup {
if (child != null) {
float scrollProgress = getScrollProgress(screenCenter, child, i);
float alpha = 1 - Math.abs(scrollProgress);
- child.setFastAlpha(alpha);
- child.fastInvalidate();
+ child.setAlpha(alpha);
}
}
invalidate();
@@ -715,20 +721,23 @@ public abstract class PagedView extends ViewGroup {
protected void getVisiblePages(int[] range) {
final int pageCount = getChildCount();
+
if (pageCount > 0) {
- final int pageWidth = getScaledMeasuredWidth(getPageAt(0));
final int screenWidth = getMeasuredWidth();
- int x = getScaledRelativeChildOffset(0) + pageWidth;
int leftScreen = 0;
int rightScreen = 0;
- while (x <= mScrollX && leftScreen < pageCount - 1) {
+ View currPage = getPageAt(leftScreen);
+ while (leftScreen < pageCount - 1 &&
+ currPage.getX() + currPage.getWidth() - currPage.getPaddingRight() < mScrollX) {
leftScreen++;
- x += getScaledMeasuredWidth(getPageAt(leftScreen)) + mPageSpacing;
+ currPage = getPageAt(leftScreen);
}
rightScreen = leftScreen;
- while (x < mScrollX + screenWidth && rightScreen < pageCount - 1) {
+ currPage = getPageAt(rightScreen + 1);
+ while (rightScreen < pageCount - 1 &&
+ currPage.getX() - currPage.getPaddingLeft() < mScrollX + screenWidth) {
rightScreen++;
- x += getScaledMeasuredWidth(getPageAt(rightScreen)) + mPageSpacing;
+ currPage = getPageAt(rightScreen + 1);
}
range[0] = leftScreen;
range[1] = rightScreen;
@@ -764,8 +773,22 @@ public abstract class PagedView extends ViewGroup {
canvas.clipRect(mScrollX, mScrollY, mScrollX + mRight - mLeft,
mScrollY + mBottom - mTop);
- for (int i = rightScreen; i >= leftScreen; i--) {
- drawChild(canvas, getPageAt(i), drawingTime);
+ // On certain graphics drivers, if you draw to a off-screen buffer that's not
+ // used, it can lead to poor performance. We were running into this when
+ // setChildrenLayersEnabled was called on a CellLayout; that triggered a re-draw
+ // of that CellLayout's hardware layer, even if that CellLayout wasn't visible.
+ // As a fix, below we set pages that aren't going to be rendered are to be
+ // View.INVISIBLE, preventing re-drawing of their hardware layer
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ final View v = getPageAt(i);
+
+ if (leftScreen <= i && i <= rightScreen &&
+ v.getAlpha() > ViewConfiguration.ALPHA_THRESHOLD) {
+ v.setVisibility(VISIBLE);
+ drawChild(canvas, v, drawingTime);
+ } else {
+ v.setVisibility(INVISIBLE);
+ }
}
canvas.restore();
}
@@ -986,19 +1009,6 @@ public abstract class PagedView extends ViewGroup {
return mTouchState != TOUCH_STATE_REST;
}
- protected void animateClickFeedback(View v, final Runnable r) {
- // animate the view slightly to show click feedback running some logic after it is "pressed"
- ObjectAnimator anim = (ObjectAnimator) AnimatorInflater.
- loadAnimator(mContext, R.anim.paged_view_click_feedback);
- anim.setTarget(v);
- anim.addListener(new AnimatorListenerAdapter() {
- public void onAnimationRepeat(Animator animation) {
- r.run();
- }
- });
- anim.start();
- }
-
protected void determineScrollingStart(MotionEvent ev) {
determineScrollingStart(ev, 1.0f);
}
@@ -1208,12 +1218,11 @@ public abstract class PagedView extends ViewGroup {
final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage));
boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
SIGNIFICANT_MOVE_THRESHOLD;
- final int snapVelocity = mSnapVelocity;
mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
- Math.abs(velocityX) > snapVelocity;
+ Math.abs(velocityX) > mFlingThresholdVelocity;
// In the case that the page is moved far to one direction and then is flung
// in the opposite direction, we use a threshold to determine whether we should
@@ -1434,7 +1443,7 @@ public abstract class PagedView extends ViewGroup {
int delta = newX - mUnboundedScrollX;
int duration = 0;
- if (Math.abs(velocity) < MIN_FLING_VELOCITY) {
+ if (Math.abs(velocity) < mMinFlingVelocity) {
// If the velocity is low enough, then treat this more as an automatic page advance
// as opposed to an apparent physical response to flinging
snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
@@ -1450,7 +1459,7 @@ public abstract class PagedView extends ViewGroup {
distanceInfluenceForSnapDuration(distanceRatio);
velocity = Math.abs(velocity);
- velocity = Math.max(MINIMUM_SNAP_VELOCITY, velocity);
+ velocity = Math.max(mMinSnapVelocity, velocity);
// we want the page's snap velocity to approximately match the velocity at which the
// user flings, so we scale the duration by a value near to the derivative of the scroll
@@ -1591,22 +1600,26 @@ public abstract class PagedView extends ViewGroup {
int upperPageBound = getAssociatedUpperPageBound(page);
if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/"
+ upperPageBound);
+ // First, clear any pages that should no longer be loaded
+ for (int i = 0; i < count; ++i) {
+ Page layout = (Page) getPageAt(i);
+ if ((i < lowerPageBound) || (i > upperPageBound)) {
+ if (layout.getPageChildCount() > 0) {
+ layout.removeAllViewsOnPage();
+ }
+ mDirtyPageContent.set(i, true);
+ }
+ }
+ // Next, load any new pages
for (int i = 0; i < count; ++i) {
if ((i != page) && immediateAndOnly) {
continue;
}
- Page layout = (Page) getPageAt(i);
- final int childCount = layout.getPageChildCount();
if (lowerPageBound <= i && i <= upperPageBound) {
if (mDirtyPageContent.get(i)) {
syncPageItems(i, (i == page) && immediateAndOnly);
mDirtyPageContent.set(i, false);
}
- } else {
- if (childCount > 0) {
- layout.removeAllViewsOnPage();
- }
- mDirtyPageContent.set(i, true);
}
}
}
@@ -1621,72 +1634,6 @@ public abstract class PagedView extends ViewGroup {
return Math.min(page + 1, count - 1);
}
- protected void startChoiceMode(int mode, ActionMode.Callback callback) {
- if (isChoiceMode(CHOICE_MODE_NONE)) {
- mChoiceMode = mode;
- mActionMode = startActionMode(callback);
- }
- }
-
- public void endChoiceMode() {
- if (!isChoiceMode(CHOICE_MODE_NONE)) {
- mChoiceMode = CHOICE_MODE_NONE;
- resetCheckedGrandchildren();
- if (mActionMode != null) mActionMode.finish();
- mActionMode = null;
- }
- }
-
- protected boolean isChoiceMode(int mode) {
- return mChoiceMode == mode;
- }
-
- protected ArrayList<Checkable> getCheckedGrandchildren() {
- ArrayList<Checkable> checked = new ArrayList<Checkable>();
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; ++i) {
- Page layout = (Page) getPageAt(i);
- final int grandChildCount = layout.getPageChildCount();
- for (int j = 0; j < grandChildCount; ++j) {
- final View v = layout.getChildOnPageAt(j);
- if (v instanceof Checkable && ((Checkable) v).isChecked()) {
- checked.add((Checkable) v);
- }
- }
- }
- return checked;
- }
-
- /**
- * If in CHOICE_MODE_SINGLE and an item is checked, returns that item.
- * Otherwise, returns null.
- */
- protected Checkable getSingleCheckedGrandchild() {
- if (mChoiceMode != CHOICE_MODE_MULTIPLE) {
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; ++i) {
- Page layout = (Page) getPageAt(i);
- final int grandChildCount = layout.getPageChildCount();
- for (int j = 0; j < grandChildCount; ++j) {
- final View v = layout.getChildOnPageAt(j);
- if (v instanceof Checkable && ((Checkable) v).isChecked()) {
- return (Checkable) v;
- }
- }
- }
- }
- return null;
- }
-
- protected void resetCheckedGrandchildren() {
- // loop through children, and set all of their children to _not_ be checked
- final ArrayList<Checkable> checked = getCheckedGrandchildren();
- for (int i = 0; i < checked.size(); ++i) {
- final Checkable c = checked.get(i);
- c.setChecked(false);
- }
- }
-
/**
* This method is called ONLY to synchronize the number of pages that the paged view has.
* To actually fill the pages with information, implement syncPageItems() below. It is
@@ -1743,12 +1690,12 @@ public abstract class PagedView extends ViewGroup {
}
}
- protected ImageView getScrollingIndicator() {
+ protected View getScrollingIndicator() {
// We use mHasScrollIndicator to prevent future lookups if there is no sibling indicator
// found
if (mHasScrollIndicator && mScrollIndicator == null) {
ViewGroup parent = (ViewGroup) getParent();
- mScrollIndicator = (ImageView) (parent.findViewById(R.id.paged_view_indicator));
+ mScrollIndicator = (View) (parent.findViewById(R.id.paged_view_indicator));
mHasScrollIndicator = mScrollIndicator != null;
if (mHasScrollIndicator) {
mScrollIndicator.setVisibility(View.VISIBLE);
@@ -1774,9 +1721,12 @@ public abstract class PagedView extends ViewGroup {
}
protected void showScrollingIndicator(boolean immediately) {
+ mShouldShowScrollIndicator = true;
+ mShouldShowScrollIndicatorImmediately = true;
if (getChildCount() <= 1) return;
if (!isScrollingIndicatorEnabled()) return;
+ mShouldShowScrollIndicator = false;
getScrollingIndicator();
if (mScrollIndicator != null) {
// Fade the indicator in
@@ -1848,6 +1798,9 @@ public abstract class PagedView extends ViewGroup {
if (mScrollIndicator != null) {
updateScrollingIndicatorPosition();
}
+ if (mShouldShowScrollIndicator) {
+ showScrollingIndicator(mShouldShowScrollIndicatorImmediately);
+ }
}
private void updateScrollingIndicatorPosition() {
@@ -1874,7 +1827,6 @@ public abstract class PagedView extends ViewGroup {
indicatorPos += indicatorCenterOffset;
}
mScrollIndicator.setTranslationX(indicatorPos);
- mScrollIndicator.invalidate();
}
public void showScrollIndicatorTrack() {
diff --git a/src/com/android/launcher2/PagedViewGridLayout.java b/src/com/android/launcher2/PagedViewGridLayout.java
index b1b621598..90bfe88ec 100644
--- a/src/com/android/launcher2/PagedViewGridLayout.java
+++ b/src/com/android/launcher2/PagedViewGridLayout.java
@@ -109,6 +109,7 @@ public class PagedViewGridLayout extends GridLayout implements Page {
@Override
public void removeAllViewsOnPage() {
removeAllViews();
+ mOnLayoutListener = null;
destroyHardwareLayer();
}
diff --git a/src/com/android/launcher2/PagedViewIcon.java b/src/com/android/launcher2/PagedViewIcon.java
index af10f189c..4149ab618 100644
--- a/src/com/android/launcher2/PagedViewIcon.java
+++ b/src/com/android/launcher2/PagedViewIcon.java
@@ -16,43 +16,28 @@
package com.android.launcher2;
-import android.animation.ObjectAnimator;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
import android.util.AttributeSet;
-import android.widget.Checkable;
import android.widget.TextView;
-import com.android.launcher.R;
-
-
/**
* An icon on a PagedView, specifically for items in the launcher's paged view (with compound
* drawables on the top).
*/
-public class PagedViewIcon extends TextView implements Checkable {
- private static final String TAG = "PagedViewIcon";
-
- // holographic outline
- private final Paint mPaint = new Paint();
- private Bitmap mCheckedOutline;
- private Bitmap mHolographicOutline;
- private Bitmap mIcon;
+public class PagedViewIcon extends TextView {
+ /** A simple callback interface to allow a PagedViewIcon to notify when it has been pressed */
+ public static interface PressedCallback {
+ void iconPressed(PagedViewIcon icon);
+ }
- private int mAlpha = 255;
- private int mHolographicAlpha;
+ private static final String TAG = "PagedViewIcon";
+ private static final float PRESS_ALPHA = 0.4f;
- private boolean mIsChecked;
- private ObjectAnimator mCheckedAlphaAnimator;
- private float mCheckedAlpha = 1.0f;
- private int mCheckedFadeInDuration;
- private int mCheckedFadeOutDuration;
+ private PagedViewIcon.PressedCallback mPressedCallback;
+ private boolean mLockDrawableState = false;
- HolographicPagedViewIcon mHolographicOutlineView;
- private HolographicOutlineHelper mHolographicOutlineHelper;
+ private Bitmap mIcon;
public PagedViewIcon(Context context) {
this(context, null);
@@ -64,131 +49,43 @@ public class PagedViewIcon extends TextView implements Checkable {
public PagedViewIcon(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
-
- // Set up fade in/out constants
- final Resources r = context.getResources();
- final int alpha = r.getInteger(R.integer.config_dragAppsCustomizeIconFadeAlpha);
- if (alpha > 0) {
- mCheckedAlpha = r.getInteger(R.integer.config_dragAppsCustomizeIconFadeAlpha) / 256.0f;
- mCheckedFadeInDuration =
- r.getInteger(R.integer.config_dragAppsCustomizeIconFadeInDuration);
- mCheckedFadeOutDuration =
- r.getInteger(R.integer.config_dragAppsCustomizeIconFadeOutDuration);
- }
-
- mHolographicOutlineView = new HolographicPagedViewIcon(context, this);
- }
-
- protected HolographicPagedViewIcon getHolographicOutlineView() {
- return mHolographicOutlineView;
- }
-
- protected Bitmap getHolographicOutline() {
- return mHolographicOutline;
}
public void applyFromApplicationInfo(ApplicationInfo info, boolean scaleUp,
- HolographicOutlineHelper holoOutlineHelper) {
- mHolographicOutlineHelper = holoOutlineHelper;
+ PagedViewIcon.PressedCallback cb) {
mIcon = info.iconBitmap;
+ mPressedCallback = cb;
setCompoundDrawablesWithIntrinsicBounds(null, new FastBitmapDrawable(mIcon), null, null);
setText(info.title);
setTag(info);
}
- public void setHolographicOutline(Bitmap holoOutline) {
- mHolographicOutline = holoOutline;
- getHolographicOutlineView().invalidate();
- }
-
- @Override
- public void setAlpha(float alpha) {
- final float viewAlpha = HolographicOutlineHelper.viewAlphaInterpolator(alpha);
- final float holographicAlpha = HolographicOutlineHelper.highlightAlphaInterpolator(alpha);
- int newViewAlpha = (int) (viewAlpha * 255);
- int newHolographicAlpha = (int) (holographicAlpha * 255);
- if ((mAlpha != newViewAlpha) || (mHolographicAlpha != newHolographicAlpha)) {
- mAlpha = newViewAlpha;
- mHolographicAlpha = newHolographicAlpha;
- super.setAlpha(viewAlpha);
- }
- }
-
- public void invalidateCheckedImage() {
- if (mCheckedOutline != null) {
- mCheckedOutline.recycle();
- mCheckedOutline = null;
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- if (mAlpha > 0) {
- super.onDraw(canvas);
- }
-
- Bitmap overlay = null;
-
- // draw any blended overlays
- if (mCheckedOutline != null) {
- mPaint.setAlpha(255);
- overlay = mCheckedOutline;
- }
-
- if (overlay != null) {
- final int offset = getScrollX();
- final int compoundPaddingLeft = getCompoundPaddingLeft();
- final int compoundPaddingRight = getCompoundPaddingRight();
- int hspace = getWidth() - compoundPaddingRight - compoundPaddingLeft;
- canvas.drawBitmap(overlay,
- offset + compoundPaddingLeft + (hspace - overlay.getWidth()) / 2,
- mPaddingTop,
- mPaint);
- }
+ public void lockDrawableState() {
+ mLockDrawableState = true;
}
- @Override
- public boolean isChecked() {
- return mIsChecked;
+ public void resetDrawableState() {
+ mLockDrawableState = false;
+ post(new Runnable() {
+ @Override
+ public void run() {
+ refreshDrawableState();
+ }
+ });
}
- void setChecked(boolean checked, boolean animate) {
- if (mIsChecked != checked) {
- mIsChecked = checked;
-
- float alpha;
- int duration;
- if (mIsChecked) {
- alpha = mCheckedAlpha;
- duration = mCheckedFadeInDuration;
- } else {
- alpha = 1.0f;
- duration = mCheckedFadeOutDuration;
- }
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
- // Initialize the animator
- if (mCheckedAlphaAnimator != null) {
- mCheckedAlphaAnimator.cancel();
+ // We keep in the pressed state until resetDrawableState() is called to reset the press
+ // feedback
+ if (isPressed()) {
+ setAlpha(PRESS_ALPHA);
+ if (mPressedCallback != null) {
+ mPressedCallback.iconPressed(this);
}
- if (animate) {
- mCheckedAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", getAlpha(), alpha);
- mCheckedAlphaAnimator.setDuration(duration);
- mCheckedAlphaAnimator.start();
- } else {
- setAlpha(alpha);
- }
-
- invalidate();
+ } else if (!mLockDrawableState) {
+ setAlpha(1f);
}
}
-
- @Override
- public void setChecked(boolean checked) {
- setChecked(checked, true);
- }
-
- @Override
- public void toggle() {
- setChecked(!mIsChecked);
- }
}
diff --git a/src/com/android/launcher2/PagedViewWidget.java b/src/com/android/launcher2/PagedViewWidget.java
index 8fcfa8fed..670717e49 100644
--- a/src/com/android/launcher2/PagedViewWidget.java
+++ b/src/com/android/launcher2/PagedViewWidget.java
@@ -16,20 +16,14 @@
package com.android.launcher2;
-import android.animation.ObjectAnimator;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.RectF;
import android.util.AttributeSet;
-import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.widget.Checkable;
+import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -39,27 +33,15 @@ import com.android.launcher.R;
/**
* The linear layout used strictly for the widget/wallpaper tab of the customization tray
*/
-public class PagedViewWidget extends LinearLayout implements Checkable {
+public class PagedViewWidget extends LinearLayout {
static final String TAG = "PagedViewWidgetLayout";
private static boolean sDeletePreviewsWhenDetachedFromWindow = true;
- private final Paint mPaint = new Paint();
- private Bitmap mHolographicOutline;
- private HolographicOutlineHelper mHolographicOutlineHelper;
- private ImageView mPreviewImageView;
- private final RectF mTmpScaleRect = new RectF();
-
private String mDimensionsFormatString;
-
- private int mAlpha = 255;
- private int mHolographicAlpha;
-
- private boolean mIsChecked;
- private ObjectAnimator mCheckedAlphaAnimator;
- private float mCheckedAlpha = 1.0f;
- private int mCheckedFadeInDuration;
- private int mCheckedFadeOutDuration;
+ CheckForShortPress mPendingCheckForShortPress = null;
+ ShortPressListener mShortPressListener = null;
+ boolean mShortPressTriggered = false;
public PagedViewWidget(Context context) {
this(context, null);
@@ -72,16 +54,7 @@ public class PagedViewWidget extends LinearLayout implements Checkable {
public PagedViewWidget(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- // Set up fade in/out constants
final Resources r = context.getResources();
- final int alpha = r.getInteger(R.integer.config_dragAppsCustomizeIconFadeAlpha);
- if (alpha > 0) {
- mCheckedAlpha = r.getInteger(R.integer.config_dragAppsCustomizeIconFadeAlpha) / 256.0f;
- mCheckedFadeInDuration =
- r.getInteger(R.integer.config_dragAppsCustomizeIconFadeInDuration);
- mCheckedFadeOutDuration =
- r.getInteger(R.integer.config_dragAppsCustomizeIconFadeOutDuration);
- }
mDimensionsFormatString = r.getString(R.string.widget_dims_format);
setWillNotDraw(false);
@@ -109,14 +82,12 @@ public class PagedViewWidget extends LinearLayout implements Checkable {
}
public void applyFromAppWidgetProviderInfo(AppWidgetProviderInfo info,
- int maxWidth, int[] cellSpan, HolographicOutlineHelper holoOutlineHelper) {
- mHolographicOutlineHelper = holoOutlineHelper;
+ int maxWidth, int[] cellSpan) {
final ImageView image = (ImageView) findViewById(R.id.widget_preview);
if (maxWidth > -1) {
image.setMaxWidth(maxWidth);
}
image.setContentDescription(info.label);
- mPreviewImageView = image;
final TextView name = (TextView) findViewById(R.id.widget_name);
name.setText(info.label);
final TextView dims = (TextView) findViewById(R.id.widget_dims);
@@ -125,13 +96,10 @@ public class PagedViewWidget extends LinearLayout implements Checkable {
}
}
- public void applyFromResolveInfo(PackageManager pm, ResolveInfo info,
- HolographicOutlineHelper holoOutlineHelper) {
- mHolographicOutlineHelper = holoOutlineHelper;
+ public void applyFromResolveInfo(PackageManager pm, ResolveInfo info) {
CharSequence label = info.loadLabel(pm);
final ImageView image = (ImageView) findViewById(R.id.widget_preview);
image.setContentDescription(label);
- mPreviewImageView = image;
final TextView name = (TextView) findViewById(R.id.widget_name);
name.setText(label);
final TextView dims = (TextView) findViewById(R.id.widget_dims);
@@ -159,111 +127,74 @@ public class PagedViewWidget extends LinearLayout implements Checkable {
}
}
- public void setHolographicOutline(Bitmap holoOutline) {
- mHolographicOutline = holoOutline;
- invalidate();
+ void setShortPressListener(ShortPressListener listener) {
+ mShortPressListener = listener;
}
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- // We eat up the touch events here, since the PagedView (which uses the same swiping
- // touch code as Workspace previously) uses onInterceptTouchEvent() to determine when
- // the user is scrolling between pages. This means that if the pages themselves don't
- // handle touch events, it gets forwarded up to PagedView itself, and it's own
- // onTouchEvent() handling will prevent further intercept touch events from being called
- // (it's the same view in that case). This is not ideal, but to prevent more changes,
- // we just always mark the touch event as handled.
- return super.onTouchEvent(event) || true;
+ interface ShortPressListener {
+ void onShortPress(View v);
+ void cleanUpShortPress(View v);
}
- @Override
- protected void onDraw(Canvas canvas) {
- if (mAlpha > 0) {
- super.onDraw(canvas);
- }
-
- // draw any blended overlays
- if (mHolographicOutline != null && mHolographicAlpha > 0) {
- // Calculate how much to scale the holographic preview
- mTmpScaleRect.set(0,0,1,1);
- mPreviewImageView.getImageMatrix().mapRect(mTmpScaleRect);
-
- mPaint.setAlpha(mHolographicAlpha);
- canvas.save();
- canvas.scale(mTmpScaleRect.right, mTmpScaleRect.bottom);
- canvas.drawBitmap(mHolographicOutline, mPreviewImageView.getLeft(),
- mPreviewImageView.getTop(), mPaint);
- canvas.restore();
+ class CheckForShortPress implements Runnable {
+ public void run() {
+ if (mShortPressListener != null) {
+ mShortPressListener.onShortPress(PagedViewWidget.this);
+ }
+ mShortPressTriggered = true;
}
}
- @Override
- protected boolean onSetAlpha(int alpha) {
- return true;
- }
-
- private void setChildrenAlpha(float alpha) {
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- getChildAt(i).setAlpha(alpha);
+ private void checkForShortPress() {
+ if (mPendingCheckForShortPress == null) {
+ mPendingCheckForShortPress = new CheckForShortPress();
}
+ postDelayed(mPendingCheckForShortPress, 120);
}
- @Override
- public void setAlpha(float alpha) {
- final float viewAlpha = mHolographicOutlineHelper.viewAlphaInterpolator(alpha);
- final float holographicAlpha = mHolographicOutlineHelper.highlightAlphaInterpolator(alpha);
- int newViewAlpha = (int) (viewAlpha * 255);
- int newHolographicAlpha = (int) (holographicAlpha * 255);
- if ((mAlpha != newViewAlpha) || (mHolographicAlpha != newHolographicAlpha)) {
- mAlpha = newViewAlpha;
- mHolographicAlpha = newHolographicAlpha;
- setChildrenAlpha(viewAlpha);
- super.setAlpha(viewAlpha);
+
+ /**
+ * Remove the longpress detection timer.
+ */
+ private void removeShortPressCallback() {
+ if (mPendingCheckForShortPress != null) {
+ removeCallbacks(mPendingCheckForShortPress);
}
}
- void setChecked(boolean checked, boolean animate) {
- if (mIsChecked != checked) {
- mIsChecked = checked;
-
- float alpha;
- int duration;
- if (mIsChecked) {
- alpha = mCheckedAlpha;
- duration = mCheckedFadeInDuration;
- } else {
- alpha = 1.0f;
- duration = mCheckedFadeOutDuration;
- }
-
- // Initialize the animator
- if (mCheckedAlphaAnimator != null) {
- mCheckedAlphaAnimator.cancel();
- }
- if (animate) {
- mCheckedAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", getAlpha(), alpha);
- mCheckedAlphaAnimator.setDuration(duration);
- mCheckedAlphaAnimator.start();
- } else {
- setAlpha(alpha);
+ private void cleanUpShortPress() {
+ removeShortPressCallback();
+ if (mShortPressTriggered) {
+ if (mShortPressListener != null) {
+ mShortPressListener.cleanUpShortPress(PagedViewWidget.this);
}
-
- invalidate();
+ mShortPressTriggered = false;
}
}
@Override
- public void setChecked(boolean checked) {
- setChecked(checked, true);
- }
-
- @Override
- public boolean isChecked() {
- return mIsChecked;
- }
-
- @Override
- public void toggle() {
- setChecked(!mIsChecked);
+ public boolean onTouchEvent(MotionEvent event) {
+ super.onTouchEvent(event);
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_UP:
+ cleanUpShortPress();
+ break;
+ case MotionEvent.ACTION_DOWN:
+ checkForShortPress();
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ cleanUpShortPress();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ break;
+ }
+ // We eat up the touch events here, since the PagedView (which uses the same swiping
+ // touch code as Workspace previously) uses onInterceptTouchEvent() to determine when
+ // the user is scrolling between pages. This means that if the pages themselves don't
+ // handle touch events, it gets forwarded up to PagedView itself, and it's own
+ // onTouchEvent() handling will prevent further intercept touch events from being called
+ // (it's the same view in that case). This is not ideal, but to prevent more changes,
+ // we just always mark the touch event as handled.
+ return true;
}
}
diff --git a/src/com/android/launcher2/PagedViewWithDraggableItems.java b/src/com/android/launcher2/PagedViewWithDraggableItems.java
index 287a06565..a0479707e 100644
--- a/src/com/android/launcher2/PagedViewWithDraggableItems.java
+++ b/src/com/android/launcher2/PagedViewWithDraggableItems.java
@@ -165,4 +165,12 @@ public abstract class PagedViewWithDraggableItems extends PagedView
cancelDragging();
super.onDetachedFromWindow();
}
+
+ /** Show the scrolling indicators when we move the page */
+ protected void onPageBeginMoving() {
+ showScrollingIndicator(false);
+ }
+ protected void onPageEndMoving() {
+ hideScrollingIndicator(false);
+ }
}
diff --git a/src/com/android/launcher2/PendingAddItemInfo.java b/src/com/android/launcher2/PendingAddItemInfo.java
index 9c52ecfa0..d36e21763 100644
--- a/src/com/android/launcher2/PendingAddItemInfo.java
+++ b/src/com/android/launcher2/PendingAddItemInfo.java
@@ -16,6 +16,7 @@
package com.android.launcher2;
+import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.os.Parcelable;
@@ -33,8 +34,12 @@ class PendingAddItemInfo extends ItemInfo {
class PendingAddWidgetInfo extends PendingAddItemInfo {
int minWidth;
int minHeight;
+ int minResizeWidth;
+ int minResizeHeight;
int previewImage;
int icon;
+ AppWidgetProviderInfo info;
+ AppWidgetHostView boundWidget;
// Any configuration data that we want to pass to a configuration activity when
// starting up a widget
@@ -43,9 +48,12 @@ class PendingAddWidgetInfo extends PendingAddItemInfo {
public PendingAddWidgetInfo(AppWidgetProviderInfo i, String dataMimeType, Parcelable data) {
itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+ this.info = i;
componentName = i.provider;
minWidth = i.minWidth;
minHeight = i.minHeight;
+ minResizeWidth = i.minResizeWidth;
+ minResizeHeight = i.minResizeHeight;
previewImage = i.previewImage;
icon = i.icon;
if (dataMimeType != null && data != null) {
@@ -53,4 +61,20 @@ class PendingAddWidgetInfo extends PendingAddItemInfo {
configurationData = data;
}
}
-} \ No newline at end of file
+
+ // Copy constructor
+ public PendingAddWidgetInfo(PendingAddWidgetInfo copy) {
+ minWidth = copy.minWidth;
+ minHeight = copy.minHeight;
+ minResizeWidth = copy.minResizeWidth;
+ minResizeHeight = copy.minResizeHeight;
+ previewImage = copy.previewImage;
+ icon = copy.icon;
+ info = copy.info;
+ boundWidget = copy.boundWidget;
+ mimeType = copy.mimeType;
+ configurationData = copy.configurationData;
+ componentName = copy.componentName;
+ itemType = copy.itemType;
+ }
+}
diff --git a/src/com/android/launcher2/RocketLauncher.java b/src/com/android/launcher2/RocketLauncher.java
index 125537464..505ac4c9e 100644
--- a/src/com/android/launcher2/RocketLauncher.java
+++ b/src/com/android/launcher2/RocketLauncher.java
@@ -23,7 +23,6 @@ package com.android.launcher2;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeAnimator;
-import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -31,6 +30,7 @@ import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
+import android.support.v13.dreams.BasicDream;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
@@ -44,7 +44,7 @@ import com.android.launcher.R;
import java.util.HashMap;
import java.util.Random;
-public class RocketLauncher extends Activity {
+public class RocketLauncher extends BasicDream {
public static final boolean ROCKET_LAUNCHER = true;
public static class Board extends FrameLayout
diff --git a/src/com/android/launcher2/SearchDropTargetBar.java b/src/com/android/launcher2/SearchDropTargetBar.java
index 3a7f24b09..76d707618 100644
--- a/src/com/android/launcher2/SearchDropTargetBar.java
+++ b/src/com/android/launcher2/SearchDropTargetBar.java
@@ -69,6 +69,7 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
dragController.addDragListener(mDeleteDropTarget);
dragController.addDropTarget(mInfoDropTarget);
dragController.addDropTarget(mDeleteDropTarget);
+ dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
mInfoDropTarget.setLauncher(launcher);
mDeleteDropTarget.setLauncher(launcher);
}
@@ -129,18 +130,37 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D
@Override
public void onAnimationStart(Animator animation) {
mQSBSearchBar.setVisibility(View.VISIBLE);
+ mQSBSearchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mQSBSearchBar.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
mQSBSearchBarFadeOutAnim = ObjectAnimator.ofFloat(mQSBSearchBar, "alpha", 0f);
mQSBSearchBarFadeOutAnim.setDuration(sTransitionOutDuration);
mQSBSearchBarFadeOutAnim.addListener(new AnimatorListenerAdapter() {
@Override
+ public void onAnimationStart(Animator animation) {
+ mQSBSearchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
mQSBSearchBar.setVisibility(View.INVISIBLE);
+ mQSBSearchBar.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
}
+ public void finishAnimations() {
+ mDropTargetBarFadeInAnim.end();
+ mDropTargetBarFadeOutAnim.end();
+ mQSBSearchBarFadeInAnim.end();
+ mQSBSearchBarFadeOutAnim.end();
+ }
+
private void cancelAnimations() {
mDropTargetBarFadeInAnim.cancel();
mDropTargetBarFadeOutAnim.cancel();
diff --git a/src/com/android/launcher2/CellLayoutChildren.java b/src/com/android/launcher2/ShortcutAndWidgetContainer.java
index 35f5af104..7e5e94072 100644
--- a/src/com/android/launcher2/CellLayoutChildren.java
+++ b/src/com/android/launcher2/ShortcutAndWidgetContainer.java
@@ -18,11 +18,15 @@ package com.android.launcher2;
import android.app.WallpaperManager;
import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
-public class CellLayoutChildren extends ViewGroup {
+import com.android.launcher2.CellLayout.LayoutParams;
+
+public class ShortcutAndWidgetContainer extends ViewGroup {
static final String TAG = "CellLayoutChildren";
// These are temporary variables to prevent having to allocate a new object just to
@@ -37,7 +41,7 @@ public class CellLayoutChildren extends ViewGroup {
private int mWidthGap;
private int mHeightGap;
- public CellLayoutChildren(Context context) {
+ public ShortcutAndWidgetContainer(Context context) {
super(context);
mWallpaperManager = WallpaperManager.getInstance(context);
}
@@ -68,6 +72,22 @@ public class CellLayoutChildren extends ViewGroup {
}
@Override
+ protected void dispatchDraw(Canvas canvas) {
+ // Debug drawing for hit space
+ if (false) {
+ Paint p = new Paint();
+ p.setColor(0x6600FF00);
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ final View child = getChildAt(i);
+ final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+
+ canvas.drawRect(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height, p);
+ }
+ }
+ super.dispatchDraw(canvas);
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
diff --git a/src/com/android/launcher2/ShortcutInfo.java b/src/com/android/launcher2/ShortcutInfo.java
index c0f80aeec..ff3028beb 100644
--- a/src/com/android/launcher2/ShortcutInfo.java
+++ b/src/com/android/launcher2/ShortcutInfo.java
@@ -93,12 +93,16 @@ class ShortcutInfo extends ItemInfo {
public Bitmap getIcon(IconCache iconCache) {
if (mIcon == null) {
- mIcon = iconCache.getIcon(this.intent);
- this.usingFallbackIcon = iconCache.isDefaultIcon(mIcon);
+ updateIcon(iconCache);
}
return mIcon;
}
+ public void updateIcon(IconCache iconCache) {
+ mIcon = iconCache.getIcon(intent);
+ usingFallbackIcon = iconCache.isDefaultIcon(mIcon);
+ }
+
/**
* Creates the application intent based on a component name and various launch flags.
* Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}.
diff --git a/src/com/android/launcher2/SmoothPagedView.java b/src/com/android/launcher2/SmoothPagedView.java
index fe763f508..e6414d960 100644
--- a/src/com/android/launcher2/SmoothPagedView.java
+++ b/src/com/android/launcher2/SmoothPagedView.java
@@ -35,11 +35,11 @@ public abstract class SmoothPagedView extends PagedView {
private Interpolator mScrollInterpolator;
- private static class WorkspaceOvershootInterpolator implements Interpolator {
+ public static class OvershootInterpolator implements Interpolator {
private static final float DEFAULT_TENSION = 1.3f;
private float mTension;
- public WorkspaceOvershootInterpolator() {
+ public OvershootInterpolator() {
mTension = DEFAULT_TENSION;
}
@@ -101,7 +101,7 @@ public abstract class SmoothPagedView extends PagedView {
if (mScrollMode == DEFAULT_MODE) {
mBaseLineFlingVelocity = 2500.0f;
mFlingVelocityInfluence = 0.4f;
- mScrollInterpolator = new WorkspaceOvershootInterpolator();
+ mScrollInterpolator = new OvershootInterpolator();
mScroller = new Scroller(getContext(), mScrollInterpolator);
}
}
@@ -139,9 +139,9 @@ public abstract class SmoothPagedView extends PagedView {
}
if (settle) {
- ((WorkspaceOvershootInterpolator) mScrollInterpolator).setDistance(screenDelta);
+ ((OvershootInterpolator) mScrollInterpolator).setDistance(screenDelta);
} else {
- ((WorkspaceOvershootInterpolator) mScrollInterpolator).disableSettle();
+ ((OvershootInterpolator) mScrollInterpolator).disableSettle();
}
velocity = Math.abs(velocity);
diff --git a/src/com/android/launcher2/SpringLoadedDragController.java b/src/com/android/launcher2/SpringLoadedDragController.java
index 358362c85..d96aab794 100644
--- a/src/com/android/launcher2/SpringLoadedDragController.java
+++ b/src/com/android/launcher2/SpringLoadedDragController.java
@@ -18,7 +18,7 @@ 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 = 550;
+ final long ENTER_SPRING_LOAD_HOVER_TIME = 500;
final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950;
final long EXIT_SPRING_LOAD_HOVER_TIME = 200;
diff --git a/src/com/android/launcher2/Utilities.java b/src/com/android/launcher2/Utilities.java
index d7562a947..0387011a4 100644
--- a/src/com/android/launcher2/Utilities.java
+++ b/src/com/android/launcher2/Utilities.java
@@ -113,7 +113,6 @@ final class Utilities {
}
int sourceWidth = icon.getIntrinsicWidth();
int sourceHeight = icon.getIntrinsicHeight();
-
if (sourceWidth > 0 && sourceHeight > 0) {
// There are intrinsic sizes.
if (width < sourceWidth || height < sourceHeight) {
diff --git a/src/com/android/launcher2/WallpaperChooserDialogFragment.java b/src/com/android/launcher2/WallpaperChooserDialogFragment.java
index 7c4ac2e33..eec699e35 100644
--- a/src/com/android/launcher2/WallpaperChooserDialogFragment.java
+++ b/src/com/android/launcher2/WallpaperChooserDialogFragment.java
@@ -82,10 +82,7 @@ public class WallpaperChooserDialogFragment extends DialogFragment implements
outState.putBoolean(EMBEDDED_KEY, mEmbedded);
}
- @Override
- public void onDestroy() {
- super.onDestroy();
-
+ private void cancelLoader() {
if (mLoader != null && mLoader.getStatus() != WallpaperLoader.Status.FINISHED) {
mLoader.cancel(true);
mLoader = null;
@@ -93,6 +90,20 @@ public class WallpaperChooserDialogFragment extends DialogFragment implements
}
@Override
+ public void onDetach() {
+ super.onDetach();
+
+ cancelLoader();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ cancelLoader();
+ }
+
+ @Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
/* On orientation changes, the dialog is effectively "dismissed" so this is called
@@ -112,26 +123,6 @@ public class WallpaperChooserDialogFragment extends DialogFragment implements
public Dialog onCreateDialog(Bundle savedInstanceState) {
findWallpapers();
- // TODO: The following code is not exercised right now and may be removed
- // if the dialog version is not needed.
- /*
- final View v = getActivity().getLayoutInflater().inflate(
- R.layout.wallpaper_chooser, null, false);
-
- GridView gridView = (GridView) v.findViewById(R.id.gallery);
- gridView.setOnItemClickListener(this);
- gridView.setAdapter(new ImageAdapter(getActivity()));
-
- final int viewInset =
- getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_inset);
-
- FrameLayout wallPaperList = (FrameLayout) v.findViewById(R.id.wallpaper_list);
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setNegativeButton(R.string.wallpaper_cancel, null);
- builder.setTitle(R.string.wallpaper_dialog_title);
- builder.setView(wallPaperList,
- viewInset, viewInset, viewInset, viewInset); return builder.create();
- */
return null;
}
@@ -366,4 +357,4 @@ public class WallpaperChooserDialogFragment extends DialogFragment implements
// Ignore
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 41e8a461b..ba8b56f20 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -17,19 +17,15 @@
package com.android.launcher2;
import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.app.AlertDialog;
import android.app.WallpaperManager;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
-import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.Context;
@@ -42,9 +38,8 @@ import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
-import android.graphics.PorterDuff;
+import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.os.IBinder;
@@ -57,16 +52,17 @@ import android.view.Display;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
import android.widget.TextView;
-import android.widget.Toast;
import com.android.launcher.R;
+import com.android.launcher2.DropTarget.DragObject;
import com.android.launcher2.FolderIcon.FolderRingAnimator;
import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
+import com.android.launcher2.LauncherSettings.Favorites;
import java.util.ArrayList;
import java.util.HashSet;
@@ -79,7 +75,7 @@ import java.util.List;
*/
public class Workspace extends SmoothPagedView
implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
- DragController.DragListener {
+ DragController.DragListener, LauncherTransitionable {
@SuppressWarnings({"UnusedDeclaration"})
private static final String TAG = "Launcher.Workspace";
@@ -94,6 +90,9 @@ public class Workspace extends SmoothPagedView
private static final int BACKGROUND_FADE_OUT_DURATION = 350;
private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
+ private static final int FLING_THRESHOLD_VELOCITY = 500;
+
+ private float mMaxDistanceForFolderCreation = 50.0f;
// These animators are used to fade the children's outlines
private ObjectAnimator mChildrenOutlineFadeInAnimation;
@@ -131,6 +130,7 @@ public class Workspace extends SmoothPagedView
* The CellLayout that is currently being dragged over
*/
private CellLayout mDragTargetLayout = null;
+ private boolean mDragHasEnteredWorkspace = false;
private Launcher mLauncher;
private IconCache mIconCache;
@@ -158,11 +158,6 @@ public class Workspace extends SmoothPagedView
enum State { NORMAL, SPRING_LOADED, SMALL };
private State mState = State.NORMAL;
private boolean mIsSwitchingState = false;
- private boolean mSwitchStateAfterFirstLayout = false;
- private State mStateAfterFirstLayout;
-
- private AnimatorSet mAnimator;
- private AnimatorListener mChangeStateAnimationListener;
boolean mAnimatingViewIntoPlace = false;
boolean mIsDragOccuring = false;
@@ -175,11 +170,8 @@ public class Workspace extends SmoothPagedView
private Bitmap mDragOutline = null;
private final Rect mTempRect = new Rect();
private final int[] mTempXY = new int[2];
- private int mDragViewMultiplyColor;
private float mOverscrollFade = 0;
-
- // Paint used to draw external drop outline
- private final Paint mExternalDragOutlinePaint = new Paint();
+ public static final int DRAG_BITMAP_PADDING = 0;
// Camera and Matrix used to determine the final position of a neighboring CellLayout
private final Matrix mMatrix = new Matrix();
@@ -192,16 +184,21 @@ public class Workspace extends SmoothPagedView
WallpaperOffsetInterpolator mWallpaperOffset;
boolean mUpdateWallpaperOffsetImmediately = false;
private Runnable mDelayedResizeRunnable;
+ private Runnable mDelayedSnapToPageRunnable;
private int mDisplayWidth;
private int mDisplayHeight;
+ private boolean mIsStaticWallpaper;
private int mWallpaperTravelWidth;
// Variables relating to the creation of user folders by hovering shortcuts over shortcuts
- private static final int FOLDER_CREATION_TIMEOUT = 250;
+ private static final int FOLDER_CREATION_TIMEOUT = 0;
+ private static final int REORDER_TIMEOUT = 250;
private final Alarm mFolderCreationAlarm = new Alarm();
+ private final Alarm mReorderAlarm = new Alarm();
private FolderRingAnimator mDragFolderRingAnimator = null;
private View mLastDragOverView = null;
private boolean mCreateUserFolderOnDrop = false;
+ private boolean mWillAddToExistingFolder = false;
// Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
private float mXDown;
@@ -210,6 +207,26 @@ public class Workspace extends SmoothPagedView
final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
+ // Relating to the animation of items being dropped externally
+ public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
+ public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
+ public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
+ public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
+ public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
+
+ // Related to dragging, folder creation and reordering
+ private static final int DRAG_MODE_NONE = 0;
+ private static final int DRAG_MODE_CREATE_FOLDER = 1;
+ private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
+ private static final int DRAG_MODE_REORDER = 3;
+ private int mDragMode = DRAG_MODE_NONE;
+ private int mLastReorderX = -1;
+ private int mLastReorderY = -1;
+
+ // Relating to workspace drag fade out
+ private float mDragFadeOutAlpha;
+ private int mDragFadeOutDuration;
+
// These variables are used for storing the initial and final values during workspace animations
private int mSavedScrollX;
private float mSavedRotationY;
@@ -261,8 +278,10 @@ public class Workspace extends SmoothPagedView
// With workspace, data is available straight from the get-go
setDataIsReady();
- mFadeInAdjacentScreens =
- getResources().getBoolean(R.bool.config_workspaceFadeAdjacentScreens);
+ final Resources res = getResources();
+ mFadeInAdjacentScreens = res.getBoolean(R.bool.config_workspaceFadeAdjacentScreens);
+ mDragFadeOutAlpha = res.getInteger(R.integer.config_dragFadeOutAlpha) / 100f;
+ mDragFadeOutDuration = res.getInteger(R.integer.config_dragFadeOutDuration);
mWallpaperManager = WallpaperManager.getInstance(context);
int cellCountX = DEFAULT_CELL_COUNT_X;
@@ -271,7 +290,6 @@ public class Workspace extends SmoothPagedView
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.Workspace, defStyle, 0);
- final Resources res = context.getResources();
if (LauncherApplication.isScreenLarge()) {
// Determine number of rows/columns dynamically
// TODO: This code currently fails on tablets with an aspect ratio < 1.3.
@@ -279,9 +297,11 @@ public class Workspace extends SmoothPagedView
// landscape
TypedArray actionBarSizeTypedArray =
context.obtainStyledAttributes(new int[] { android.R.attr.actionBarSize });
+ DisplayMetrics displayMetrics = res.getDisplayMetrics();
final float actionBarHeight = actionBarSizeTypedArray.getDimension(0, 0f);
final float systemBarHeight = res.getDimension(R.dimen.status_bar_height);
- final float smallestScreenDim = res.getConfiguration().smallestScreenWidthDp;
+ final float smallestScreenDim = res.getConfiguration().smallestScreenWidthDp *
+ displayMetrics.density;
cellCountX = 1;
while (CellLayout.widthInPortrait(res, cellCountX + 1) <= smallestScreenDim) {
@@ -297,7 +317,6 @@ public class Workspace extends SmoothPagedView
mSpringLoadedShrinkFactor =
res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
- mDragViewMultiplyColor = res.getColor(R.color.drag_view_multiply_color);
// if the value is manually specified, use that instead
cellCountX = a.getInt(R.styleable.Workspace_cellCountX, cellCountX);
@@ -318,13 +337,13 @@ public class Workspace extends SmoothPagedView
// estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
// dimension if unsuccessful
public int[] estimateItemSize(int hSpan, int vSpan,
- PendingAddItemInfo pendingItemInfo, boolean springLoaded) {
+ ItemInfo itemInfo, boolean springLoaded) {
int[] size = new int[2];
if (getChildCount() > 0) {
CellLayout cl = (CellLayout) mLauncher.getWorkspace().getChildAt(0);
- RectF r = estimateItemPosition(cl, pendingItemInfo, 0, 0, hSpan, vSpan);
- size[0] = (int) r.width();
- size[1] = (int) r.height();
+ Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
+ size[0] = r.width();
+ size[1] = r.height();
if (springLoaded) {
size[0] *= mSpringLoadedShrinkFactor;
size[1] *= mSpringLoadedShrinkFactor;
@@ -336,19 +355,10 @@ public class Workspace extends SmoothPagedView
return size;
}
}
- public RectF estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
+ public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
int hCell, int vCell, int hSpan, int vSpan) {
- RectF r = new RectF();
+ Rect r = new Rect();
cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
- if (pendingInfo instanceof PendingAddWidgetInfo) {
- PendingAddWidgetInfo widgetInfo = (PendingAddWidgetInfo) pendingInfo;
- Rect p = AppWidgetHostView.getDefaultPaddingForWidget(mContext,
- widgetInfo.componentName, null);
- r.top += p.top;
- r.left += p.left;
- r.right -= p.right;
- r.bottom -= p.bottom;
- }
return r;
}
@@ -357,7 +367,7 @@ public class Workspace extends SmoothPagedView
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
CellLayout cl = (CellLayout) getChildAt(i);
- cl.buildChildrenLayer();
+ cl.getShortcutsAndWidgets().buildLayer();
}
}
}
@@ -383,39 +393,25 @@ public class Workspace extends SmoothPagedView
Launcher.setScreen(mCurrentPage);
LauncherApplication app = (LauncherApplication)context.getApplicationContext();
mIconCache = app.getIconCache();
- mExternalDragOutlinePaint.setAntiAlias(true);
setWillNotDraw(false);
setChildrenDrawnWithCacheEnabled(true);
+ final Resources res = getResources();
try {
- final Resources res = getResources();
mBackground = res.getDrawable(R.drawable.apps_customize_bg);
} catch (Resources.NotFoundException e) {
// In this case, we will skip drawing background protection
}
- mChangeStateAnimationListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mIsSwitchingState = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mIsSwitchingState = false;
- mWallpaperOffset.setOverrideHorizontalCatchupConstant(false);
- mAnimator = null;
- updateChildrenLayersEnabled();
- }
- };
-
- mSnapVelocity = 600;
mWallpaperOffset = new WallpaperOffsetInterpolator();
Display display = mLauncher.getWindowManager().getDefaultDisplay();
mDisplayWidth = display.getWidth();
mDisplayHeight = display.getHeight();
mWallpaperTravelWidth = (int) (mDisplayWidth *
wallpaperTravelToScreenWidthRatio(mDisplayWidth, mDisplayHeight));
+
+ mMaxDistanceForFolderCreation = (0.5f * res.getDimensionPixelSize(R.dimen.app_icon_size));
+ mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
}
@Override
@@ -521,10 +517,12 @@ public class Workspace extends SmoothPagedView
child.setOnKeyListener(new IconKeyEventListener());
}
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
- if (lp == null) {
+ LayoutParams genericLp = child.getLayoutParams();
+ CellLayout.LayoutParams lp;
+ if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
} else {
+ lp = (CellLayout.LayoutParams) genericLp;
lp.cellX = x;
lp.cellY = y;
lp.cellHSpan = spanX;
@@ -597,20 +595,26 @@ public class Workspace extends SmoothPagedView
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
- return (isSmall() || mIsSwitchingState);
+ return (isSmall() || !isFinishedSwitchingState());
}
public boolean isSwitchingState() {
return mIsSwitchingState;
}
+ /** This differs from isSwitchingState in that we take into account how far the transition
+ * has completed. */
+ public boolean isFinishedSwitchingState() {
+ return !mIsSwitchingState || (mTransitionProgress > 0.5f);
+ }
+
protected void onWindowVisibilityChanged (int visibility) {
mLauncher.onWindowVisibilityChanged(visibility);
}
@Override
public boolean dispatchUnhandledMove(View focused, int direction) {
- if (isSmall() || mIsSwitchingState) {
+ if (isSmall() || !isFinishedSwitchingState()) {
// when the home screens are shrunken, shouldn't allow side-scrolling
return false;
}
@@ -638,41 +642,42 @@ public class Workspace extends SmoothPagedView
@Override
protected void determineScrollingStart(MotionEvent ev) {
- if (!isSmall() && !mIsSwitchingState) {
- float deltaX = Math.abs(ev.getX() - mXDown);
- float deltaY = Math.abs(ev.getY() - mYDown);
+ if (isSmall()) return;
+ if (!isFinishedSwitchingState()) return;
- if (Float.compare(deltaX, 0f) == 0) return;
+ float deltaX = Math.abs(ev.getX() - mXDown);
+ float deltaY = Math.abs(ev.getY() - mYDown);
- float slope = deltaY / deltaX;
- float theta = (float) Math.atan(slope);
+ if (Float.compare(deltaX, 0f) == 0) return;
- if (deltaX > mTouchSlop || deltaY > mTouchSlop) {
- cancelCurrentPageLongPress();
- }
+ float slope = deltaY / deltaX;
+ float theta = (float) Math.atan(slope);
- if (theta > MAX_SWIPE_ANGLE) {
- // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
- return;
- } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
- // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
- // increase the touch slop to make it harder to begin scrolling the workspace. This
- // results in vertically scrolling widgets to more easily. The higher the angle, the
- // more we increase touch slop.
- theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
- float extraRatio = (float)
- Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
- super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
- } else {
- // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
- super.determineScrollingStart(ev);
- }
+ if (deltaX > mTouchSlop || deltaY > mTouchSlop) {
+ cancelCurrentPageLongPress();
+ }
+
+ if (theta > MAX_SWIPE_ANGLE) {
+ // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
+ return;
+ } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
+ // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
+ // increase the touch slop to make it harder to begin scrolling the workspace. This
+ // results in vertically scrolling widgets to more easily. The higher the angle, the
+ // more we increase touch slop.
+ theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
+ float extraRatio = (float)
+ Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
+ super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
+ } else {
+ // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
+ super.determineScrollingStart(ev);
}
}
@Override
protected boolean isScrollingIndicatorEnabled() {
- return mState != State.SPRING_LOADED;
+ return super.isScrollingIndicatorEnabled() && (mState != State.SPRING_LOADED);
}
protected void onPageBeginMoving() {
@@ -694,7 +699,19 @@ public class Workspace extends SmoothPagedView
// Only show page outlines as we pan if we are on large screen
if (LauncherApplication.isScreenLarge()) {
showOutlines();
+ mIsStaticWallpaper = mWallpaperManager.getWallpaperInfo() == null;
}
+
+ // If we are not fading in adjacent screens, we still need to restore the alpha in case the
+ // user scrolls while we are transitioning (should not affect dispatchDraw optimizations)
+ if (!mFadeInAdjacentScreens) {
+ for (int i = 0; i < getChildCount(); ++i) {
+ ((CellLayout) getPageAt(i)).setShortcutAndWidgetAlpha(1f);
+ }
+ }
+
+ // Show the scroll indicator as you pan the page
+ showScrollingIndicator(false);
}
protected void onPageEndMoving() {
@@ -706,12 +723,23 @@ public class Workspace extends SmoothPagedView
clearChildrenCache();
}
- // Hide the outlines, as long as we're not dragging
- if (!mDragController.dragging()) {
- // Only hide page outlines as we pan if we are on large screen
+
+ if (mDragController.isDragging()) {
+ if (isSmall()) {
+ // If we are in springloaded mode, then force an event to check if the current touch
+ // is under a new page (to scroll to)
+ mDragController.forceMoveEvent();
+ }
+ } else {
+ // If we are not mid-dragging, hide the page outlines if we are on a large screen
if (LauncherApplication.isScreenLarge()) {
hideOutlines();
}
+
+ // Hide the scroll indicator as you pan the page
+ if (!mDragController.isDragging()) {
+ hideScrollingIndicator(false);
+ }
}
mOverScrollMaxBackgroundAlpha = 0.0f;
mOverScrollPageIndex = -1;
@@ -720,6 +748,11 @@ public class Workspace extends SmoothPagedView
mDelayedResizeRunnable.run();
mDelayedResizeRunnable = null;
}
+
+ if (mDelayedSnapToPageRunnable != null) {
+ mDelayedSnapToPageRunnable.run();
+ mDelayedSnapToPageRunnable = null;
+ }
}
@Override
@@ -782,28 +815,7 @@ public class Workspace extends SmoothPagedView
}.start();
}
- public void setVerticalWallpaperOffset(float offset) {
- mWallpaperOffset.setFinalY(offset);
- }
- public float getVerticalWallpaperOffset() {
- return mWallpaperOffset.getCurrY();
- }
- public void setHorizontalWallpaperOffset(float offset) {
- mWallpaperOffset.setFinalX(offset);
- }
- public float getHorizontalWallpaperOffset() {
- return mWallpaperOffset.getCurrX();
- }
-
private float wallpaperOffsetForCurrentScroll() {
- // The wallpaper travel width is how far, from left to right, the wallpaper will move
- // at this orientation. On tablets in portrait mode we don't move all the way to the
- // edges of the wallpaper, or otherwise the parallax effect would be too strong.
- int wallpaperTravelWidth = mWallpaperWidth;
- if (LauncherApplication.isScreenLarge()) {
- wallpaperTravelWidth = mWallpaperTravelWidth;
- }
-
// Set wallpaper offset steps (1 / (number of screens - 1))
mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 1.0f);
@@ -821,11 +833,22 @@ public class Workspace extends SmoothPagedView
float scrollProgress =
adjustedScrollX / (float) scrollRange;
- float offsetInDips = wallpaperTravelWidth * scrollProgress +
- (mWallpaperWidth - wallpaperTravelWidth) / 2; // center it
- float offset = offsetInDips / (float) mWallpaperWidth;
- return offset;
+
+ if (LauncherApplication.isScreenLarge() && mIsStaticWallpaper) {
+ // The wallpaper travel width is how far, from left to right, the wallpaper will move
+ // at this orientation. On tablets in portrait mode we don't move all the way to the
+ // edges of the wallpaper, or otherwise the parallax effect would be too strong.
+ int wallpaperTravelWidth = Math.min(mWallpaperTravelWidth, mWallpaperWidth);
+
+ float offsetInDips = wallpaperTravelWidth * scrollProgress +
+ (mWallpaperWidth - wallpaperTravelWidth) / 2; // center it
+ float offset = offsetInDips / (float) mWallpaperWidth;
+ return offset;
+ } else {
+ return scrollProgress;
+ }
}
+
private void syncWallpaperOffsetWithScroll() {
final boolean enableWallpaperEffects = isHardwareAccelerated();
if (enableWallpaperEffects) {
@@ -855,7 +878,7 @@ public class Workspace extends SmoothPagedView
}
}
if (keepUpdating) {
- fastInvalidate();
+ invalidate();
}
}
@@ -871,6 +894,20 @@ public class Workspace extends SmoothPagedView
computeWallpaperScrollRatio(whichPage);
}
+ @Override
+ protected void snapToPage(int whichPage, int duration) {
+ super.snapToPage(whichPage, duration);
+ computeWallpaperScrollRatio(whichPage);
+ }
+
+ protected void snapToPage(int whichPage, Runnable r) {
+ if (mDelayedSnapToPageRunnable != null) {
+ mDelayedSnapToPageRunnable.run();
+ }
+ mDelayedSnapToPageRunnable = r;
+ snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION);
+ }
+
private void computeWallpaperScrollRatio(int page) {
// Here, we determine what the desired scroll would be with and without a layout scale,
// and compute a ratio between the two. This allows us to adjust the wallpaper offset
@@ -1167,13 +1204,12 @@ public class Workspace extends SmoothPagedView
backgroundAlphaInterpolator(Math.abs(scrollProgress)));
}
}
- cl.setFastTranslationX(translationX);
- cl.setFastRotationY(rotation);
+ cl.setTranslationX(translationX);
+ cl.setRotationY(rotation);
if (mFadeInAdjacentScreens && !isSmall()) {
float alpha = 1 - Math.abs(scrollProgress);
- cl.setFastAlpha(alpha);
+ cl.setShortcutAndWidgetAlpha(alpha);
}
- cl.fastInvalidate();
}
}
if (!isSwitchingState() && !isInOverscroll) {
@@ -1246,19 +1282,6 @@ public class Workspace extends SmoothPagedView
mUpdateWallpaperOffsetImmediately = true;
}
super.onLayout(changed, left, top, right, bottom);
-
- // if shrinkToBottom() is called on initialization, it has to be deferred
- // until after the first call to onLayout so that it has the correct width
- if (mSwitchStateAfterFirstLayout) {
- mSwitchStateAfterFirstLayout = false;
- // shrink can trigger a synchronous onLayout call, so we
- // post this to avoid a stack overflow / tangled onLayout calls
- post(new Runnable() {
- public void run() {
- changeState(mStateAfterFirstLayout, false);
- }
- });
- }
}
@Override
@@ -1281,32 +1304,6 @@ public class Workspace extends SmoothPagedView
return (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground);
}
- public void scrollTo (int x, int y) {
- super.scrollTo(x, y);
- syncChildrenLayersEnabledOnVisiblePages();
- }
-
- // This method just applies the value mChildrenLayersEnabled to all the pages that
- // will be rendered on the next frame.
- // We do this because calling setChildrenLayersEnabled on a view that's not
- // visible/rendered causes slowdowns on some graphics cards
- private void syncChildrenLayersEnabledOnVisiblePages() {
- if (mChildrenLayersEnabled) {
- getVisiblePages(mTempVisiblePagesRange);
- final int leftScreen = mTempVisiblePagesRange[0];
- final int rightScreen = mTempVisiblePagesRange[1];
- if (leftScreen != -1 && rightScreen != -1) {
- for (int i = leftScreen; i <= rightScreen; i++) {
- ViewGroup page = (ViewGroup) getPageAt(i);
- if (page.getVisibility() == VISIBLE &&
- page.getAlpha() > ViewConfiguration.ALPHA_THRESHOLD) {
- ((ViewGroup)getPageAt(i)).setChildrenLayersEnabled(true);
- }
- }
- }
- }
- }
-
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
@@ -1321,8 +1318,9 @@ public class Workspace extends SmoothPagedView
final int paddingTop = mPaddingTop + offset;
final int paddingBottom = mPaddingBottom + offset;
- final CellLayout leftPage = (CellLayout) getChildAt(mCurrentPage - 1);
- final CellLayout rightPage = (CellLayout) getChildAt(mCurrentPage + 1);
+ final int page = (mNextPage != INVALID_PAGE ? mNextPage : mCurrentPage);
+ final CellLayout leftPage = (CellLayout) getChildAt(page - 1);
+ final CellLayout rightPage = (CellLayout) getChildAt(page + 1);
if (leftPage != null && leftPage.getIsDragOverlapping()) {
final Drawable d = getResources().getDrawable(R.drawable.page_hover_left_holo);
@@ -1407,19 +1405,13 @@ public class Workspace extends SmoothPagedView
}
private void updateChildrenLayersEnabled() {
- boolean small = isSmall() || mIsSwitchingState;
- boolean dragging = mAnimatingViewIntoPlace || mIsDragOccuring;
- boolean enableChildrenLayers = small || dragging || isPageMoving();
+ boolean small = mState == State.SMALL || mIsSwitchingState;
+ boolean enableChildrenLayers = small || mAnimatingViewIntoPlace || isPageMoving();
if (enableChildrenLayers != mChildrenLayersEnabled) {
mChildrenLayersEnabled = enableChildrenLayers;
- // calling setChildrenLayersEnabled on a view that's not visible/rendered
- // causes slowdowns on some graphics cards, so we only disable it here and leave
- // the enabling to dispatchDraw
- if (!enableChildrenLayers) {
- for (int i = 0; i < getPageCount(); i++) {
- ((ViewGroup)getChildAt(i)).setChildrenLayersEnabled(false);
- }
+ for (int i = 0; i < getPageCount(); i++) {
+ ((ViewGroup)getChildAt(i)).setChildrenLayersEnabled(mChildrenLayersEnabled);
}
}
}
@@ -1442,7 +1434,7 @@ public class Workspace extends SmoothPagedView
* This interpolator emulates the rate at which the perceived scale of an object changes
* as its distance from a camera increases. When this interpolator is applied to a scale
* animation on a view, it evokes the sense that the object is shrinking due to moving away
- * from the camera.
+ * from the camera.
*/
static class ZInterpolator implements TimeInterpolator {
private float focalLength;
@@ -1508,23 +1500,18 @@ public class Workspace extends SmoothPagedView
public void onDragStartedWithItem(View v) {
final Canvas canvas = new Canvas();
- // We need to add extra padding to the bitmap to make room for the glow effect
- final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
-
// The outline is used to visualize where the item will land if dropped
- mDragOutline = createDragOutline(v, canvas, bitmapPadding);
+ mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING);
}
public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, Paint alphaClipPaint) {
final Canvas canvas = new Canvas();
- // We need to add extra padding to the bitmap to make room for the glow effect
- final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
-
int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
// The outline is used to visualize where the item will land if dropped
- mDragOutline = createDragOutline(b, canvas, bitmapPadding, size[0], size[1], alphaClipPaint);
+ mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, size[0],
+ size[1], alphaClipPaint);
}
// we call this method whenever a drag and drop in Launcher finishes, even if Workspace was
@@ -1562,38 +1549,26 @@ public class Workspace extends SmoothPagedView
mNewRotationYs = new float[childCount];
}
- public void changeState(State shrinkState) {
- changeState(shrinkState, true);
+ Animator getChangeStateAnimation(final State state, boolean animated) {
+ return getChangeStateAnimation(state, animated, 0);
}
- void changeState(final State state, boolean animated) {
- changeState(state, animated, 0);
- }
-
- void changeState(final State state, boolean animated, int delay) {
+ Animator getChangeStateAnimation(final State state, boolean animated, int delay) {
if (mState == state) {
- return;
- }
- if (mFirstLayout) {
- // (mFirstLayout == "first layout has not happened yet")
- // cancel any pending shrinks that were set earlier
- mSwitchStateAfterFirstLayout = false;
- mStateAfterFirstLayout = state;
- return;
+ return null;
}
// Initialize animation arrays for the first time if necessary
initAnimationArrays();
- // Cancel any running transition animations
- if (mAnimator != null) mAnimator.cancel();
- mAnimator = new AnimatorSet();
+ AnimatorSet anim = animated ? new AnimatorSet() : null;
// Stop any scrolling, move to the current page right away
setCurrentPage((mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage);
final State oldState = mState;
final boolean oldStateIsNormal = (oldState == State.NORMAL);
+ final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
final boolean oldStateIsSmall = (oldState == State.SMALL);
mState = state;
final boolean stateIsNormal = (state == State.NORMAL);
@@ -1619,7 +1594,7 @@ public class Workspace extends SmoothPagedView
setLayoutScale(1.0f);
}
- final int duration = zoomIn ?
+ final int duration = zoomIn ?
getResources().getInteger(R.integer.config_workspaceUnshrinkTime) :
getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
for (int i = 0; i < getChildCount(); i++) {
@@ -1634,8 +1609,9 @@ public class Workspace extends SmoothPagedView
if ((oldStateIsSmall && stateIsNormal) ||
(oldStateIsNormal && stateIsSmall)) {
// To/from workspace - only show the current page unless the transition is not
- // animated and the animation end callback below doesn't run
- if (i == mCurrentPage || !animated) {
+ // animated and the animation end callback below doesn't run;
+ // or, if we're in spring-loaded mode
+ if (i == mCurrentPage || !animated || oldStateIsSpringLoaded) {
finalAlpha = 1f;
finalAlphaMultiplierValue = 0f;
} else {
@@ -1685,83 +1661,73 @@ public class Workspace extends SmoothPagedView
cl.setScaleY(finalScaleFactor);
cl.setBackgroundAlpha(finalBackgroundAlpha);
cl.setBackgroundAlphaMultiplier(finalAlphaMultiplierValue);
- cl.setAlpha(finalAlpha);
+ cl.setShortcutAndWidgetAlpha(finalAlpha);
cl.setRotationY(rotation);
- mChangeStateAnimationListener.onAnimationEnd(null);
}
}
if (animated) {
- ValueAnimator animWithInterpolator =
- ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
-
- if (zoomIn) {
- animWithInterpolator.setInterpolator(mZoomInInterpolator);
- }
-
- animWithInterpolator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(android.animation.Animator animation) {
- // The above code to determine initialAlpha and finalAlpha will ensure that only
- // the current page is visible during (and subsequently, after) the transition
- // animation. If fade adjacent pages is disabled, then re-enable the page
- // visibility after the transition animation.
- if (!mFadeInAdjacentScreens && stateIsNormal && oldStateIsSmall) {
- for (int i = 0; i < getChildCount(); i++) {
- final CellLayout cl = (CellLayout) getChildAt(i);
- cl.setAlpha(1f);
- }
- }
- }
- });
- animWithInterpolator.addUpdateListener(new LauncherAnimatorUpdateListener() {
- public void onAnimationUpdate(float a, float b) {
- mTransitionProgress = b;
- if (b == 0f) {
- // an optimization, but not required
- return;
- }
- invalidate();
- for (int i = 0; i < getChildCount(); i++) {
- final CellLayout cl = (CellLayout) getChildAt(i);
- cl.invalidate();
- cl.setFastTranslationX(a * mOldTranslationXs[i] + b * mNewTranslationXs[i]);
- cl.setFastTranslationY(a * mOldTranslationYs[i] + b * mNewTranslationYs[i]);
- cl.setFastScaleX(a * mOldScaleXs[i] + b * mNewScaleXs[i]);
- cl.setFastScaleY(a * mOldScaleYs[i] + b * mNewScaleYs[i]);
- cl.setFastBackgroundAlpha(
- a * mOldBackgroundAlphas[i] + b * mNewBackgroundAlphas[i]);
- cl.setBackgroundAlphaMultiplier(a * mOldBackgroundAlphaMultipliers[i] +
- b * mNewBackgroundAlphaMultipliers[i]);
- cl.setFastAlpha(a * mOldAlphas[i] + b * mNewAlphas[i]);
- cl.invalidate();
+ for (int index = 0; index < getChildCount(); index++) {
+ final int i = index;
+ final CellLayout cl = (CellLayout) getChildAt(i);
+ if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
+ cl.setTranslationX(mNewTranslationXs[i]);
+ cl.setTranslationY(mNewTranslationYs[i]);
+ cl.setScaleX(mNewScaleXs[i]);
+ cl.setScaleY(mNewScaleYs[i]);
+ cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
+ cl.setBackgroundAlphaMultiplier(mNewBackgroundAlphaMultipliers[i]);
+ cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
+ cl.setRotationY(mNewRotationYs[i]);
+ } else {
+ LauncherViewPropertyAnimator a = new LauncherViewPropertyAnimator(cl);
+ a.translationX(mNewTranslationXs[i])
+ .translationY(mNewTranslationYs[i])
+ .scaleX(mNewScaleXs[i])
+ .scaleY(mNewScaleYs[i])
+ .setDuration(duration)
+ .setInterpolator(mZoomInInterpolator);
+ anim.play(a);
+
+ if (mOldAlphas[i] != mNewAlphas[i]) {
+ LauncherViewPropertyAnimator alphaAnim =
+ new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
+ alphaAnim.alpha(mNewAlphas[i])
+ .setDuration(duration)
+ .setInterpolator(mZoomInInterpolator);
+ anim.play(alphaAnim);
}
- syncChildrenLayersEnabledOnVisiblePages();
- }
- });
-
- ValueAnimator rotationAnim =
- ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
- rotationAnim.setInterpolator(new DecelerateInterpolator(2.0f));
- rotationAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
- public void onAnimationUpdate(float a, float b) {
- if (b == 0f) {
- // an optimization, but not required
- return;
+ if (mOldRotationYs[i] != 0 || mNewRotationYs[i] != 0) {
+ ValueAnimator rotate = ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
+ rotate.setInterpolator(new DecelerateInterpolator(2.0f));
+ rotate.addUpdateListener(new LauncherAnimatorUpdateListener() {
+ public void onAnimationUpdate(float a, float b) {
+ cl.setRotationY(a * mOldRotationYs[i] + b * mNewRotationYs[i]);
+ }
+ });
+ anim.play(rotate);
}
- for (int i = 0; i < getChildCount(); i++) {
- final CellLayout cl = (CellLayout) getChildAt(i);
- cl.setFastRotationY(a * mOldRotationYs[i] + b * mNewRotationYs[i]);
+ if (mOldBackgroundAlphas[i] != 0 ||
+ mNewBackgroundAlphas[i] != 0 ||
+ mOldBackgroundAlphaMultipliers[i] != 0 ||
+ mNewBackgroundAlphaMultipliers[i] != 0) {
+ ValueAnimator bgAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
+ bgAnim.setInterpolator(mZoomInInterpolator);
+ bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
+ public void onAnimationUpdate(float a, float b) {
+ cl.setBackgroundAlpha(
+ a * mOldBackgroundAlphas[i] +
+ b * mNewBackgroundAlphas[i]);
+ cl.setBackgroundAlphaMultiplier(
+ a * mOldBackgroundAlphaMultipliers[i] +
+ b * mNewBackgroundAlphaMultipliers[i]);
+ }
+ });
+ anim.play(bgAnim);
}
}
- });
-
- mAnimator.playTogether(animWithInterpolator, rotationAnim);
- mAnimator.setStartDelay(delay);
- // If we call this when we're not animated, onAnimationEnd is never called on
- // the listener; make sure we only use the listener when we're actually animating
- mAnimator.addListener(mChangeStateAnimationListener);
- mAnimator.start();
+ }
+ anim.setStartDelay(delay);
}
if (stateIsSpringLoaded) {
@@ -1774,7 +1740,40 @@ public class Workspace extends SmoothPagedView
// Fade the background gradient away
animateBackgroundGradient(0f, true);
}
- syncChildrenLayersEnabledOnVisiblePages();
+ return anim;
+ }
+
+ @Override
+ public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+ mIsSwitchingState = true;
+ cancelScrollingIndicatorAnimations();
+ }
+
+ @Override
+ public void onLauncherTransitionStep(Launcher l, float t) {
+ mTransitionProgress = t;
+ }
+
+ @Override
+ public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+ mIsSwitchingState = false;
+ mWallpaperOffset.setOverrideHorizontalCatchupConstant(false);
+ updateChildrenLayersEnabled();
+ // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure
+ // ensure that only the current page is visible during (and subsequently, after) the
+ // transition animation. If fade adjacent pages is disabled, then re-enable the page
+ // visibility after the transition animation.
+ if (!mFadeInAdjacentScreens) {
+ for (int i = 0; i < getChildCount(); i++) {
+ final CellLayout cl = (CellLayout) getChildAt(i);
+ cl.setShortcutAndWidgetAlpha(1f);
+ }
+ }
+ }
+
+ @Override
+ public View getContent() {
+ return this;
}
/**
@@ -1844,8 +1843,6 @@ public class Workspace extends SmoothPagedView
canvas.setBitmap(b);
drawDragView(v, canvas, padding, true);
- mOutlineHelper.applyOuterBlur(b, canvas, outlineColor);
- canvas.drawColor(mDragViewMultiplyColor, PorterDuff.Mode.MULTIPLY);
canvas.setBitmap(null);
return b;
@@ -1895,29 +1892,6 @@ public class Workspace extends SmoothPagedView
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
- * clip description mime data.
- */
- private Bitmap createExternalDragOutline(Canvas canvas, int padding) {
- Resources r = getResources();
- final int outlineColor = r.getColor(android.R.color.holo_blue_light);
- final int iconWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
- final int iconHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
- final int rectRadius = r.getDimensionPixelSize(R.dimen.external_drop_icon_rect_radius);
- final int inset = (int) (Math.min(iconWidth, iconHeight) * 0.2f);
- final Bitmap b = Bitmap.createBitmap(
- iconWidth + padding, iconHeight + padding, Bitmap.Config.ARGB_8888);
-
- canvas.setBitmap(b);
- canvas.drawRoundRect(new RectF(inset, inset, iconWidth - inset, iconHeight - inset),
- rectRadius, rectRadius, mExternalDragOutlinePaint);
- mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
- canvas.setBitmap(null);
- return b;
- }
-
void startDrag(CellLayout.CellInfo cellInfo) {
View child = cellInfo.cell;
@@ -1927,35 +1901,35 @@ public class Workspace extends SmoothPagedView
}
mDragInfo = cellInfo;
- child.setVisibility(GONE);
+ child.setVisibility(INVISIBLE);
+ CellLayout layout = (CellLayout) child.getParent().getParent();
+ layout.prepareChildForDrag(child);
child.clearFocus();
child.setPressed(false);
final Canvas canvas = new Canvas();
- // We need to add extra padding to the bitmap to make room for the glow effect
- final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
-
// The outline is used to visualize where the item will land if dropped
- mDragOutline = createDragOutline(child, canvas, bitmapPadding);
+ mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING);
beginDragShared(child, this);
}
public void beginDragShared(View child, DragSource source) {
Resources r = getResources();
- // We need to add extra padding to the bitmap to make room for the glow effect
- final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
-
// The drag bitmap follows the touch point around on the screen
- final Bitmap b = createDragBitmap(child, new Canvas(), bitmapPadding);
+ final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);
final int bmpWidth = b.getWidth();
+ final int bmpHeight = b.getHeight();
mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
- final int dragLayerX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;
- int dragLayerY = mTempXY[1] - bitmapPadding / 2;
+ int dragLayerX =
+ Math.round(mTempXY[0] - (bmpWidth - child.getScaleX() * child.getWidth()) / 2);
+ int dragLayerY =
+ Math.round(mTempXY[1] - (bmpHeight - child.getScaleY() * bmpHeight) / 2
+ - DRAG_BITMAP_PADDING / 2);
Point dragVisualizeOffset = null;
Rect dragRect = null;
@@ -1969,7 +1943,8 @@ public class Workspace extends SmoothPagedView
dragLayerY += top;
// Note: The drag region is used to calculate drag layer offsets, but the
// dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
- dragVisualizeOffset = new Point(-bitmapPadding / 2, iconPaddingTop - bitmapPadding / 2);
+ dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2,
+ iconPaddingTop - DRAG_BITMAP_PADDING / 2);
dragRect = new Rect(left, top, right, bottom);
} else if (child instanceof FolderIcon) {
int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);
@@ -1983,8 +1958,11 @@ public class Workspace extends SmoothPagedView
}
mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
- DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);
+ DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, child.getScaleX());
b.recycle();
+
+ // Show the scrolling indicator when you pick up an item
+ showScrollingIndicator(false);
}
void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, int screen,
@@ -1999,7 +1977,7 @@ public class Workspace extends SmoothPagedView
}
public boolean transitionStateShouldAllowDrop() {
- return (!isSwitchingState() || mTransitionProgress > 0.5f);
+ return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL);
}
/**
@@ -2026,56 +2004,78 @@ public class Workspace extends SmoothPagedView
int spanX = 1;
int spanY = 1;
- View ignoreView = null;
if (mDragInfo != null) {
final CellLayout.CellInfo dragCellInfo = mDragInfo;
spanX = dragCellInfo.spanX;
spanY = dragCellInfo.spanY;
- ignoreView = dragCellInfo.cell;
} else {
final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
spanX = dragInfo.spanX;
spanY = dragInfo.spanY;
}
+ int minSpanX = spanX;
+ int minSpanY = spanY;
+ if (d.dragInfo instanceof PendingAddWidgetInfo) {
+ minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
+ minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
+ }
+
mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
- (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell);
- if (willCreateUserFolder((ItemInfo) d.dragInfo, mDragTargetLayout, mTargetCell, true)) {
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
+ mTargetCell);
+ float distance = mDragTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+ mDragViewVisualCenter[1], mTargetCell);
+ if (willCreateUserFolder((ItemInfo) d.dragInfo, mDragTargetLayout,
+ mTargetCell, distance, true)) {
return true;
}
if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, mDragTargetLayout,
- mTargetCell)) {
+ mTargetCell, distance)) {
return true;
}
+ int[] resultSpan = new int[2];
+ mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
+ null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
+ boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
+
// Don't accept the drop if there's no room for the item
- if (!mDragTargetLayout.findCellForSpanIgnoring(null, spanX, spanY, ignoreView)) {
+ if (!foundCell) {
// Don't show the message if we are dropping on the AllApps button and the hotseat
// is full
- if (mTargetCell != null && mLauncher.isHotseatLayout(mDragTargetLayout)) {
+ boolean isHotseat = mLauncher.isHotseatLayout(mDragTargetLayout);
+ if (mTargetCell != null && isHotseat) {
Hotseat hotseat = mLauncher.getHotseat();
- if (Hotseat.isAllAppsButtonRank(
+ if (hotseat.isAllAppsButtonRank(
hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
return false;
}
}
- mLauncher.showOutOfSpaceMessage();
+ mLauncher.showOutOfSpaceMessage(isHotseat);
return false;
}
}
return true;
}
- boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell,
- boolean considerTimeout) {
+ boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
+ distance, boolean considerTimeout) {
+ if (distance > mMaxDistanceForFolderCreation) return false;
View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
+ if (dropOverView != null) {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
+ if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
+ return false;
+ }
+ }
+
boolean hasntMoved = false;
if (mDragInfo != null) {
- CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
- hasntMoved = (mDragInfo.cellX == targetCell[0] &&
- mDragInfo.cellY == targetCell[1]) && (cellParent == target);
+ hasntMoved = dropOverView == mDragInfo.cell;
}
if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
@@ -2090,8 +2090,18 @@ public class Workspace extends SmoothPagedView
return (aboveShortcut && willBecomeShortcut);
}
- boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell) {
+ boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
+ float distance) {
+ if (distance > mMaxDistanceForFolderCreation) return false;
View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
+
+ if (dropOverView != null) {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
+ if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
+ return false;
+ }
+ }
+
if (dropOverView instanceof FolderIcon) {
FolderIcon fi = (FolderIcon) dropOverView;
if (fi.acceptDrop(dragInfo)) {
@@ -2102,8 +2112,11 @@ public class Workspace extends SmoothPagedView
}
boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
- int[] targetCell, boolean external, DragView dragView, Runnable postAnimationRunnable) {
+ int[] targetCell, float distance, boolean external, DragView dragView,
+ Runnable postAnimationRunnable) {
+ if (distance > mMaxDistanceForFolderCreation) return false;
View v = target.getChildAt(targetCell[0], targetCell[1]);
+
boolean hasntMoved = false;
if (mDragInfo != null) {
CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
@@ -2152,8 +2165,13 @@ public class Workspace extends SmoothPagedView
}
boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
- DragObject d, boolean external) {
+ float distance, DragObject d, boolean external) {
+ if (distance > mMaxDistanceForFolderCreation) return false;
+
View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
+ if (!mWillAddToExistingFolder) return false;
+ mWillAddToExistingFolder = false;
+
if (dropOverView instanceof FolderIcon) {
FolderIcon fi = (FolderIcon) dropOverView;
if (fi.acceptDrop(d.dragInfo)) {
@@ -2169,7 +2187,7 @@ public class Workspace extends SmoothPagedView
return false;
}
- public void onDrop(DragObject d) {
+ public void onDrop(final DragObject d) {
mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
mDragViewVisualCenter);
@@ -2185,6 +2203,7 @@ public class Workspace extends SmoothPagedView
CellLayout dropTargetLayout = mDragTargetLayout;
int snapScreen = -1;
+ boolean resizeOnDrop = false;
if (d.dragSource != this) {
final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1] };
@@ -2192,6 +2211,7 @@ public class Workspace extends SmoothPagedView
} else if (mDragInfo != null) {
final View cell = mDragInfo.cell;
+ Runnable resizeRunnable = null;
if (dropTargetLayout != null) {
// Move internally
boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
@@ -2205,44 +2225,67 @@ public class Workspace extends SmoothPagedView
int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
// First we find the cell nearest to point at which the item is
// dropped, without any consideration to whether there is an item there.
+
mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
+ float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+ mDragViewVisualCenter[1], mTargetCell);
+
// If the item being dropped is a shortcut and the nearest drop
// cell also contains a shortcut, then create a folder with the two shortcuts.
if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
- dropTargetLayout, mTargetCell, false, d.dragView, null)) {
+ dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
return;
}
- if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, d, false)) {
+ if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
+ distance, d, false)) {
return;
}
// Aside from the special case where we're dropping a shortcut onto a shortcut,
// we need to find the nearest cell location that is vacant
- mTargetCell = findNearestVacantArea((int) mDragViewVisualCenter[0],
- (int) mDragViewVisualCenter[1], mDragInfo.spanX, mDragInfo.spanY, cell,
- dropTargetLayout, mTargetCell);
+ ItemInfo item = (ItemInfo) d.dragInfo;
+ int minSpanX = item.spanX;
+ int minSpanY = item.spanY;
+ if (item.minSpanX > 0 && item.minSpanY > 0) {
+ minSpanX = item.minSpanX;
+ minSpanY = item.minSpanY;
+ }
+
+ int[] resultSpan = new int[2];
+ mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
+ mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
+
+ boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
+ if (foundCell && (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
+ resizeOnDrop = true;
+ item.spanX = resultSpan[0];
+ item.spanY = resultSpan[1];
+ }
if (mCurrentPage != screen && !hasMovedIntoHotseat) {
snapScreen = screen;
snapToPage(screen);
}
- if (mTargetCell[0] >= 0 && mTargetCell[1] >= 0) {
+ if (foundCell) {
+ final ItemInfo info = (ItemInfo) cell.getTag();
if (hasMovedLayouts) {
// Reparent the view
getParentCellLayoutForView(cell).removeView(cell);
addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1],
- mDragInfo.spanX, mDragInfo.spanY);
+ info.spanX, info.spanY);
}
// update the item's position after drop
- final ItemInfo info = (ItemInfo) cell.getTag();
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
- dropTargetLayout.onMove(cell, mTargetCell[0], mTargetCell[1]);
- lp.cellX = mTargetCell[0];
- lp.cellY = mTargetCell[1];
+ lp.cellX = lp.tmpCellX = mTargetCell[0];
+ lp.cellY = lp.tmpCellY = mTargetCell[1];
+ lp.cellHSpan = item.spanX;
+ lp.cellVSpan = item.spanY;
+ lp.isLockedToGrid = true;
cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screen,
mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
@@ -2254,19 +2297,20 @@ public class Workspace extends SmoothPagedView
final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
- if (pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
- final Runnable resizeRunnable = new Runnable() {
+ if (pinfo != null &&
+ pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
+ final Runnable addResizeFrame = new Runnable() {
public void run() {
DragLayer dragLayer = mLauncher.getDragLayer();
dragLayer.addResizeFrame(info, hostView, cellLayout);
}
};
- post(new Runnable() {
+ resizeRunnable = (new Runnable() {
public void run() {
if (!isPageMoving()) {
- resizeRunnable.run();
+ addResizeFrame.run();
} else {
- mDelayedResizeRunnable = resizeRunnable;
+ mDelayedResizeRunnable = addResizeFrame;
}
}
});
@@ -2275,28 +2319,43 @@ public class Workspace extends SmoothPagedView
LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX,
lp.cellY);
+ } else {
+ // If we can't find a drop location, we return the item to its original position
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
+ mTargetCell[0] = lp.cellX;
+ mTargetCell[1] = lp.cellY;
}
}
final CellLayout parent = (CellLayout) cell.getParent().getParent();
-
+ final Runnable finalResizeRunnable = resizeRunnable;
// Prepare it to be animated into its new position
// This must be called after the view has been re-parented
- final Runnable disableHardwareLayersRunnable = new Runnable() {
+ final Runnable onCompleteRunnable = new Runnable() {
@Override
public void run() {
mAnimatingViewIntoPlace = false;
updateChildrenLayersEnabled();
+ if (finalResizeRunnable != null) {
+ finalResizeRunnable.run();
+ }
}
};
mAnimatingViewIntoPlace = true;
if (d.dragView.hasDrawn()) {
- int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
- setFinalScrollForPageChange(snapScreen);
- mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
- disableHardwareLayersRunnable);
- resetFinalScrollForPageChange(snapScreen);
+ final ItemInfo info = (ItemInfo) cell.getTag();
+ if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
+ int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
+ ANIMATE_INTO_POSITION_AND_DISAPPEAR;
+ animateWidgetDrop(info, parent, d.dragView,
+ onCompleteRunnable, animationType, cell, false);
+ } else {
+ int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
+ mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
+ onCompleteRunnable, this);
+ }
} else {
+ d.deferDragViewCleanupPostAnimation = false;
cell.setVisibility(VISIBLE);
}
parent.onDropChild(cell);
@@ -2339,6 +2398,7 @@ public class Workspace extends SmoothPagedView
}
public void onDragEnter(DragObject d) {
+ mDragHasEnteredWorkspace = true;
if (mDragTargetLayout != null) {
mDragTargetLayout.setIsDragOverlapping(false);
mDragTargetLayout.onDragExit();
@@ -2358,6 +2418,13 @@ public class Workspace extends SmoothPagedView
// Clean up folders
cleanupFolderCreation(d);
+ // Clean up reorder
+ if (mReorderAlarm != null) {
+ mReorderAlarm.cancelAlarm();
+ mLastReorderX = -1;
+ mLastReorderY = -1;
+ }
+
// Reset the scroll area and previous drag target
onResetScrollArea();
@@ -2366,6 +2433,7 @@ public class Workspace extends SmoothPagedView
mDragTargetLayout.onDragExit();
}
mLastDragOverView = null;
+ mDragMode = DRAG_MODE_NONE;
mSpringLoadedDragController.cancel();
if (!mIsPageMoving) {
@@ -2374,6 +2442,7 @@ public class Workspace extends SmoothPagedView
}
public void onDragExit(DragObject d) {
+ mDragHasEnteredWorkspace = false;
doDragExit(d);
}
@@ -2405,116 +2474,6 @@ public class Workspace extends SmoothPagedView
return null;
}
- /**
- * Global drag and drop handler
- */
- @Override
- public boolean onDragEvent(DragEvent event) {
- final ClipDescription desc = event.getClipDescription();
- final CellLayout layout = (CellLayout) getChildAt(mCurrentPage);
- final int[] pos = new int[2];
- layout.getLocationOnScreen(pos);
- // We need to offset the drag coordinates to layout coordinate space
- final int x = (int) event.getX() - pos[0];
- final int y = (int) event.getY() - pos[1];
-
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_STARTED: {
- // Validate this drag
- Pair<Integer, List<WidgetMimeTypeHandlerData>> test = validateDrag(event);
- if (test != null) {
- boolean isShortcut = (test.second == null);
- if (isShortcut) {
- // Check if we have enough space on this screen to add a new shortcut
- if (!layout.findCellForSpan(pos, 1, 1)) {
- mLauncher.showOutOfSpaceMessage();
- return false;
- }
- }
- } else {
- // Show error message if we couldn't accept any of the items
- Toast.makeText(mContext, mContext.getString(R.string.external_drop_widget_error),
- Toast.LENGTH_SHORT).show();
- return false;
- }
-
- // Create the drag outline
- // We need to add extra padding to the bitmap to make room for the glow effect
- final Canvas canvas = new Canvas();
- final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
- mDragOutline = createExternalDragOutline(canvas, bitmapPadding);
-
- // Show the current page outlines to indicate that we can accept this drop
- showOutlines();
- layout.onDragEnter();
- layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1, null, null);
-
- return true;
- }
- case DragEvent.ACTION_DRAG_LOCATION:
- // Visualize the drop location
- layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1, null, null);
- return true;
- case DragEvent.ACTION_DROP: {
- // Try and add any shortcuts
- final LauncherModel model = mLauncher.getModel();
- final ClipData data = event.getClipData();
-
- // We assume that the mime types are ordered in descending importance of
- // representation. So we enumerate the list of mime types and alert the
- // user if any widgets can handle the drop. Only the most preferred
- // representation will be handled.
- pos[0] = x;
- pos[1] = y;
- Pair<Integer, List<WidgetMimeTypeHandlerData>> test = validateDrag(event);
- if (test != null) {
- final int index = test.first;
- final List<WidgetMimeTypeHandlerData> widgets = test.second;
- final boolean isShortcut = (widgets == null);
- final String mimeType = desc.getMimeType(index);
- if (isShortcut) {
- final Intent intent = data.getItemAt(index).getIntent();
- Object info = model.infoFromShortcutIntent(mContext, intent, data.getIcon());
- if (info != null) {
- onDropExternal(new int[] { x, y }, info, layout, false);
- }
- } else {
- if (widgets.size() == 1) {
- // If there is only one item, then go ahead and add and configure
- // that widget
- final AppWidgetProviderInfo widgetInfo = widgets.get(0).widgetInfo;
- final PendingAddWidgetInfo createInfo =
- new PendingAddWidgetInfo(widgetInfo, mimeType, data);
- mLauncher.addAppWidgetFromDrop(createInfo,
- LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentPage, null, pos);
- } else {
- // Show the widget picker dialog if there is more than one widget
- // that can handle this data type
- final InstallWidgetReceiver.WidgetListAdapter adapter =
- new InstallWidgetReceiver.WidgetListAdapter(mLauncher, mimeType,
- data, widgets, layout, mCurrentPage, pos);
- final AlertDialog.Builder builder =
- new AlertDialog.Builder(mContext);
- builder.setAdapter(adapter, adapter);
- builder.setCancelable(true);
- builder.setTitle(mContext.getString(
- R.string.external_drop_widget_pick_title));
- builder.setIcon(R.drawable.ic_no_applications);
- builder.show();
- }
- }
- }
- return true;
- }
- case DragEvent.ACTION_DRAG_ENDED:
- // Hide the page outlines after the drop
- layout.onDragExit();
- hideOutlines();
- return true;
- }
- return super.onDragEvent(event);
- }
-
/*
*
* Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
@@ -2540,7 +2499,11 @@ public class Workspace extends SmoothPagedView
v.getMatrix().invert(mTempInverseMatrix);
cachedInverseMatrix = mTempInverseMatrix;
}
- xy[0] = xy[0] + mScrollX - v.getLeft();
+ int scrollX = mScrollX;
+ if (mNextPage != INVALID_PAGE) {
+ scrollX = mScroller.getFinalX();
+ }
+ xy[0] = xy[0] + scrollX - v.getLeft();
xy[1] = xy[1] + mScrollY - v.getTop();
cachedInverseMatrix.mapPoints(xy);
}
@@ -2562,7 +2525,11 @@ public class Workspace extends SmoothPagedView
*/
void mapPointFromChildToSelf(View v, float[] xy) {
v.getMatrix().mapPoints(xy);
- xy[0] -= (mScrollX - v.getLeft());
+ int scrollX = mScrollX;
+ if (mNextPage != INVALID_PAGE) {
+ scrollX = mScroller.getFinalX();
+ }
+ xy[0] -= (scrollX - v.getLeft());
xy[1] -= (mScrollY - v.getTop());
}
@@ -2707,8 +2674,7 @@ public class Workspace extends SmoothPagedView
public void onDragOver(DragObject d) {
// Skip drag over events while we are dragging over side pages
- if (mInScrollArea) return;
- if (mIsSwitchingState) return;
+ if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return;
Rect r = new Rect();
CellLayout layout = null;
@@ -2719,6 +2685,7 @@ public class Workspace extends SmoothPagedView
mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
d.dragView, mDragViewVisualCenter);
+ final View child = (mDragInfo == null) ? null : mDragInfo.cell;
// Identify whether we have dragged over a side page
if (isSmall()) {
if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
@@ -2744,6 +2711,7 @@ public class Workspace extends SmoothPagedView
mDragTargetLayout.onDragEnter();
} else {
mLastDragOverView = null;
+ mDragMode = DRAG_MODE_NONE;
}
boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
@@ -2779,8 +2747,6 @@ public class Workspace extends SmoothPagedView
// Handle the drag over
if (mDragTargetLayout != null) {
- final View child = (mDragInfo == null) ? null : mDragInfo.cell;
-
// We want the point to be mapped to the dragTarget.
if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter);
@@ -2791,42 +2757,108 @@ public class Workspace extends SmoothPagedView
mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1], 1, 1, mDragTargetLayout, mTargetCell);
+ float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
+ mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
+
final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
mTargetCell[1]);
- boolean userFolderPending = willCreateUserFolder(info, mDragTargetLayout,
- mTargetCell, false);
- boolean isOverFolder = dragOverView instanceof FolderIcon;
- if (dragOverView != mLastDragOverView) {
- cancelFolderCreation();
- if (mLastDragOverView != null && mLastDragOverView instanceof FolderIcon) {
- ((FolderIcon) mLastDragOverView).onDragExit(d.dragInfo);
+ final View lastDragOverView = mLastDragOverView;
+ if (mLastDragOverView != dragOverView) {
+ mDragMode = DRAG_MODE_NONE;
+ mLastDragOverView = dragOverView;
+ if (mReorderAlarm != null) {
+ mReorderAlarm.cancelAlarm();
}
}
- if (userFolderPending && dragOverView != mLastDragOverView) {
- mFolderCreationAlarm.setOnAlarmListener(new
- FolderCreationAlarmListener(mDragTargetLayout, mTargetCell[0], mTargetCell[1]));
- mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
- }
+ boolean folder = willCreateOrAddToFolder(info, mDragTargetLayout, mTargetCell,
+ targetCellDistance, dragOverView, lastDragOverView);
- if (dragOverView != mLastDragOverView && isOverFolder) {
- ((FolderIcon) dragOverView).onDragEnter(d.dragInfo);
- if (mDragTargetLayout != null) {
- mDragTargetLayout.clearDragOutlines();
- }
+ int minSpanX = item.spanX;
+ int minSpanY = item.spanY;
+ if (item.minSpanX > 0 && item.minSpanY > 0) {
+ minSpanX = item.minSpanX;
+ minSpanY = item.minSpanY;
}
- mLastDragOverView = dragOverView;
- if (!mCreateUserFolderOnDrop && !isOverFolder) {
+ int[] reorderPosition = new int[2];
+ reorderPosition = findNearestArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], item.spanX, item.spanY, mDragTargetLayout,
+ reorderPosition);
+
+ if (!mDragTargetLayout.isNearestDropLocationOccupied((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], item.spanX, item.spanY, child, mTargetCell)) {
+ // If the current hover area isn't occupied (permanently) by any items, then we
+ // reset all the reordering.
+ mDragTargetLayout.revertTempState();
+ mDragMode = DRAG_MODE_NONE;
+ mLastDragOverView = dragOverView;
+ if (mReorderAlarm != null) {
+ mReorderAlarm.cancelAlarm();
+ }
+ mLastReorderX = -1;
+ mLastReorderY = -1;
mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
(int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
- item.spanX, item.spanY, d.dragView.getDragVisualizeOffset(),
- d.dragView.getDragRegion());
+ mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
+ d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
+ } else if (!folder && !mReorderAlarm.alarmPending() &&
+ (mLastReorderX != reorderPosition[0] || mLastReorderY != reorderPosition[1])) {
+ // Otherwise, if we aren't adding to or creating a folder and there's no pending
+ // reorder, then we schedule a reorder
+ cancelFolderCreation();
+ ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
+ minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
+ mReorderAlarm.setOnAlarmListener(listener);
+ mReorderAlarm.setAlarm(REORDER_TIMEOUT);
+ } else if (folder) {
+ if (mReorderAlarm != null) {
+ mReorderAlarm.cancelAlarm();
+ }
+ mDragTargetLayout.revertTempState();
+ mLastReorderX = -1;
+ mLastReorderY = -1;
}
}
}
+ private boolean willCreateOrAddToFolder(ItemInfo info, CellLayout targetLayout,
+ int[] targetCell, float distance, View dragOverView, View lastDragOverView) {
+ boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
+ false);
+
+ if (userFolderPending && mDragMode == DRAG_MODE_NONE) {
+ mFolderCreationAlarm.setOnAlarmListener(new
+ FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
+ mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
+ }
+
+ boolean willAddToFolder =
+ willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
+
+ if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
+ FolderIcon fi = ((FolderIcon) dragOverView);
+ mDragMode = DRAG_MODE_ADD_TO_FOLDER;
+ mWillAddToExistingFolder = true;
+ fi.onDragEnter(info);
+ if (targetLayout != null) {
+ targetLayout.clearDragOutlines();
+ }
+ }
+
+ if (dragOverView != lastDragOverView || (mCreateUserFolderOnDrop && !userFolderPending)
+ || (!willAddToFolder && mDragMode == DRAG_MODE_ADD_TO_FOLDER)) {
+ cancelFolderCreation();
+ mWillAddToExistingFolder = false;
+ if (lastDragOverView != null && lastDragOverView instanceof FolderIcon) {
+ ((FolderIcon) lastDragOverView).onDragExit(info);
+ }
+ }
+
+ return (willAddToFolder || userFolderPending) && mDragMode != DRAG_MODE_REORDER;
+ }
+
private void cleanupFolderCreation(DragObject d) {
if (mDragFolderRingAnimator != null && mCreateUserFolderOnDrop) {
mDragFolderRingAnimator.animateToNaturalState();
@@ -2868,6 +2900,51 @@ public class Workspace extends SmoothPagedView
layout.showFolderAccept(mDragFolderRingAnimator);
layout.clearDragOutlines();
mCreateUserFolderOnDrop = true;
+ mDragMode = DRAG_MODE_CREATE_FOLDER;
+ }
+ }
+
+ class ReorderAlarmListener implements OnAlarmListener {
+ float[] dragViewCenter;
+ int minSpanX, minSpanY, spanX, spanY;
+ DragView dragView;
+ View child;
+
+ public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
+ int spanY, DragView dragView, View child) {
+ this.dragViewCenter = dragViewCenter;
+ this.minSpanX = minSpanX;
+ this.minSpanY = minSpanY;
+ this.spanX = spanX;
+ this.spanY = spanY;
+ this.child = child;
+ this.dragView = dragView;
+ }
+
+ public void onAlarm(Alarm alarm) {
+ int[] resultSpan = new int[2];
+ mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell);
+ mLastReorderX = mTargetCell[0];
+ mLastReorderY = mTargetCell[1];
+
+ mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
+ child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
+
+ if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
+ mDragTargetLayout.revertTempState();
+ }
+
+ if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
+ }
+ mDragMode = DRAG_MODE_REORDER;
+
+ boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
+ mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
+ (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
+ mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
+ dragView.getDragVisualizeOffset(), dragView.getDragRegion());
}
}
@@ -2887,7 +2964,7 @@ public class Workspace extends SmoothPagedView
onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
return true;
}
- mLauncher.showOutOfSpaceMessage();
+ mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
return false;
}
@@ -2909,7 +2986,7 @@ public class Workspace extends SmoothPagedView
final Runnable exitSpringLoadedRunnable = new Runnable() {
@Override
public void run() {
- mLauncher.exitSpringLoadedDragModeDelayed(true, false);
+ mLauncher.exitSpringLoadedDragModeDelayed(true, false, null);
}
};
@@ -2937,15 +3014,29 @@ public class Workspace extends SmoothPagedView
if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
cellLayout, mTargetCell);
+ float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+ mDragViewVisualCenter[1], mTargetCell);
if (willCreateUserFolder((ItemInfo) d.dragInfo, mDragTargetLayout, mTargetCell,
- true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
- mDragTargetLayout, mTargetCell)) {
+ distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
+ mDragTargetLayout, mTargetCell, distance)) {
findNearestVacantCell = false;
}
}
+
+ final ItemInfo item = (ItemInfo) d.dragInfo;
if (findNearestVacantCell) {
- mTargetCell = findNearestVacantArea(touchXY[0], touchXY[1], spanX, spanY, null,
- cellLayout, mTargetCell);
+ int minSpanX = item.spanX;
+ int minSpanY = item.spanY;
+ if (item.minSpanX > 0 && item.minSpanY > 0) {
+ minSpanX = item.minSpanX;
+ minSpanY = item.minSpanY;
+ }
+ int[] resultSpan = new int[2];
+ mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
+ null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
+ item.spanX = resultSpan[0];
+ item.spanY = resultSpan[1];
}
Runnable onAnimationCompleteRunnable = new Runnable() {
@@ -2955,8 +3046,11 @@ public class Workspace extends SmoothPagedView
// widgets/shortcuts/folders in a slightly different way
switch (pendingInfo.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ int span[] = new int[2];
+ span[0] = item.spanX;
+ span[1] = item.spanY;
mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
- container, screen, mTargetCell, null);
+ container, screen, mTargetCell, span, null);
break;
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
mLauncher.processShortcutFromDrop(pendingInfo.componentName,
@@ -2969,28 +3063,15 @@ public class Workspace extends SmoothPagedView
cellLayout.onDragExit();
}
};
-
- // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
- // location and size on the home screen.
- RectF r = estimateItemPosition(cellLayout, pendingInfo,
- mTargetCell[0], mTargetCell[1], spanX, spanY);
- int loc[] = new int[2];
- loc[0] = (int) r.left;
- loc[1] = (int) r.top;
- setFinalTransitionTransform(cellLayout);
- float cellLayoutScale =
- mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(cellLayout, loc);
- resetTransitionTransform(cellLayout);
-
- float dragViewScale = Math.min(r.width() / d.dragView.getMeasuredWidth(),
- r.height() / d.dragView.getMeasuredHeight());
- // The animation will scale the dragView about its center, so we need to center about
- // the final location.
- loc[0] -= (d.dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
- loc[1] -= (d.dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
-
- mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, loc,
- dragViewScale * cellLayoutScale, onAnimationCompleteRunnable);
+ View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+ ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
+ int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
+ if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
+ ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
+ animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
+ }
+ animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
+ animationStyle, finalView, true);
} else {
// This is for other drag/drop cases, like dragging from All Apps
View view = null;
@@ -3018,20 +3099,24 @@ public class Workspace extends SmoothPagedView
if (touchXY != null) {
mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
cellLayout, mTargetCell);
+ float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+ mDragViewVisualCenter[1], mTargetCell);
d.postAnimationRunnable = exitSpringLoadedRunnable;
- if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, true,
- d.dragView, d.postAnimationRunnable)) {
+ if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
+ true, d.dragView, d.postAnimationRunnable)) {
return;
}
- if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, d, true)) {
+ if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
+ true)) {
return;
}
}
if (touchXY != null) {
// when dragging and dropping, just find the closest free spot
- mTargetCell = findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, null,
- cellLayout, mTargetCell);
+ mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
+ null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
} else {
cellLayout.findCellForSpan(mTargetCell, 1, 1);
}
@@ -3039,7 +3124,8 @@ public class Workspace extends SmoothPagedView
info.spanY, insertAtFirst);
cellLayout.onDropChild(view);
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
- cellLayout.getChildrenLayout().measureChild(view);
+ cellLayout.getShortcutsAndWidgets().measureChild(view);
+
LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen,
lp.cellX, lp.cellY);
@@ -3056,6 +3142,109 @@ public class Workspace extends SmoothPagedView
}
}
+ public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
+ int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
+ widgetInfo.spanY, widgetInfo, false);
+ int visibility = layout.getVisibility();
+ layout.setVisibility(VISIBLE);
+
+ int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
+ int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
+ Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
+ Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(b);
+
+ layout.measure(width, height);
+ layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
+ layout.draw(c);
+ c.setBitmap(null);
+ layout.setVisibility(visibility);
+ return b;
+ }
+
+ private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
+ DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell, View finalView,
+ boolean external) {
+ // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
+ // location and size on the home screen.
+ int spanX = info.spanX;
+ int spanY = info.spanY;
+
+ Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
+ loc[0] = r.left;
+ loc[1] = r.top;
+
+ setFinalTransitionTransform(layout);
+ float cellLayoutScale =
+ mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc);
+ resetTransitionTransform(layout);
+ float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
+ float dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
+
+ // The animation will scale the dragView about its center, so we need to center about
+ // the final location.
+ loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
+ loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
+
+ scaleXY[0] = dragViewScaleX * cellLayoutScale;
+ scaleXY[1] = dragViewScaleY * cellLayoutScale;
+ }
+
+ public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
+ final Runnable onCompleteRunnable, int animationType, final View finalView,
+ boolean external) {
+ Rect from = new Rect();
+ mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
+
+ int[] finalPos = new int[2];
+ float scaleXY[] = new float[2];
+ getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
+ finalView, external);
+
+ Resources res = mLauncher.getResources();
+ int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
+
+ // In the case where we've prebound the widget, we remove it from the DragLayer
+ if (finalView instanceof AppWidgetHostView && external) {
+ mLauncher.getDragLayer().removeView(finalView);
+ }
+ if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
+ Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
+ dragView.setCrossFadeBitmap(crossFadeBitmap);
+ dragView.crossFade((int) (duration * 0.8f));
+ } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
+ scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]);
+ }
+
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
+ mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
+ DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
+ } else {
+ int endStyle;
+ if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
+ endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
+ } else {
+ endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
+ }
+
+ Runnable onComplete = new Runnable() {
+ @Override
+ public void run() {
+ if (finalView != null) {
+ finalView.setVisibility(VISIBLE);
+ }
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ }
+ };
+ dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
+ finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
+ duration, this);
+ }
+ }
+
public void setFinalTransitionTransform(CellLayout layout) {
if (isSwitchingState()) {
int index = indexOfChild(layout);
@@ -3109,17 +3298,6 @@ public class Workspace extends SmoothPagedView
*
* pixelX and pixelY should be in the coordinate system of layout
*/
- private int[] findNearestVacantArea(int pixelX, int pixelY,
- int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
- return layout.findNearestVacantArea(
- pixelX, pixelY, spanX, spanY, ignoreView, recycle);
- }
-
- /**
- * Calculate the nearest cell where the given object would be dropped.
- *
- * pixelX and pixelY should be in the coordinate system of layout
- */
private int[] findNearestArea(int pixelX, int pixelY,
int spanX, int spanY, CellLayout layout, int[] recycle) {
return layout.findNearestArea(
@@ -3139,7 +3317,8 @@ public class Workspace extends SmoothPagedView
/**
* Called at the end of a drag which originated on the workspace.
*/
- public void onDropCompleted(View target, DragObject d, boolean success) {
+ public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
+ boolean success) {
if (success) {
if (target != this) {
if (mDragInfo != null) {
@@ -3167,6 +3346,46 @@ public class Workspace extends SmoothPagedView
}
mDragOutline = null;
mDragInfo = null;
+
+ // Hide the scrolling indicator after you pick up an item
+ hideScrollingIndicator(false);
+ }
+
+ void updateItemLocationsInDatabase(CellLayout cl) {
+ int count = cl.getShortcutsAndWidgets().getChildCount();
+
+ int screen = indexOfChild(cl);
+ int container = Favorites.CONTAINER_DESKTOP;
+
+ if (mLauncher.isHotseatLayout(cl)) {
+ screen = -1;
+ container = Favorites.CONTAINER_HOTSEAT;
+ }
+
+ for (int i = 0; i < count; i++) {
+ View v = cl.getShortcutsAndWidgets().getChildAt(i);
+ ItemInfo info = (ItemInfo) v.getTag();
+ // Null check required as the AllApps button doesn't have an item info
+ if (info != null) {
+ LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, info.cellX,
+ info.cellY);
+ }
+ }
+ }
+
+ @Override
+ public boolean supportsFlingToDelete() {
+ return true;
+ }
+
+ @Override
+ public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+ // Do nothing
+ }
+
+ @Override
+ public void onFlingToDeleteCompleted() {
+ // Do nothing
}
public boolean isDropEnabled() {
@@ -3204,7 +3423,8 @@ public class Workspace extends SmoothPagedView
@Override
public boolean onEnterScrollArea(int x, int y, int direction) {
// Ignore the scroll area if we are dragging over the hot seat
- if (mLauncher.getHotseat() != null) {
+ boolean isPortrait = !LauncherApplication.isScreenLandscape(getContext());
+ if (mLauncher.getHotseat() != null && isPortrait) {
Rect r = new Rect();
mLauncher.getHotseat().getHitRect(r);
if (r.contains(x, y)) {
@@ -3216,11 +3436,12 @@ public class Workspace extends SmoothPagedView
if (!isSmall() && !mIsSwitchingState) {
mInScrollArea = true;
- final int page = mCurrentPage + (direction == DragController.SCROLL_LEFT ? -1 : 1);
- final CellLayout layout = (CellLayout) getChildAt(page);
+ final int page = (mNextPage != INVALID_PAGE ? mNextPage : mCurrentPage) +
+ (direction == DragController.SCROLL_LEFT ? -1 : 1);
cancelFolderCreation();
- if (layout != null) {
+ if (0 <= page && page < getChildCount()) {
+ CellLayout layout = (CellLayout) getChildAt(page);
// Exit the current layout and mark the overlapping layout
if (mDragTargetLayout != null) {
mDragTargetLayout.setIsDragOverlapping(false);
@@ -3243,16 +3464,17 @@ public class Workspace extends SmoothPagedView
boolean result = false;
if (mInScrollArea) {
if (mDragTargetLayout != null) {
- // Unmark the overlapping layout and re-enter the current layout
mDragTargetLayout.setIsDragOverlapping(false);
- mDragTargetLayout = getCurrentDropLayout();
- mDragTargetLayout.onDragEnter();
-
// Workspace is responsible for drawing the edge glow on adjacent pages,
// so we need to redraw the workspace when this may have changed.
invalidate();
- result = true;
}
+ if (mDragTargetLayout != null && mDragHasEnteredWorkspace) {
+ // Unmark the overlapping layout and re-enter the current layout
+ mDragTargetLayout = getCurrentDropLayout();
+ mDragTargetLayout.onDragEnter();
+ }
+ result = true;
mInScrollArea = false;
}
return result;
@@ -3276,7 +3498,7 @@ public class Workspace extends SmoothPagedView
CellLayout getParentCellLayoutForView(View v) {
ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
for (CellLayout layout : layouts) {
- if (layout.getChildrenLayout().indexOfChild(v) > -1) {
+ if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
return layout;
}
}
@@ -3300,23 +3522,26 @@ public class Workspace extends SmoothPagedView
/**
* We should only use this to search for specific children. Do not use this method to modify
- * CellLayoutChildren directly.
+ * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
+ * the hotseat and workspace pages
*/
- ArrayList<CellLayoutChildren> getWorkspaceAndHotseatCellLayoutChildren() {
- ArrayList<CellLayoutChildren> childrenLayouts = new ArrayList<CellLayoutChildren>();
+ ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+ new ArrayList<ShortcutAndWidgetContainer>();
int screenCount = getChildCount();
for (int screen = 0; screen < screenCount; screen++) {
- childrenLayouts.add(((CellLayout) getChildAt(screen)).getChildrenLayout());
+ childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
}
if (mLauncher.getHotseat() != null) {
- childrenLayouts.add(mLauncher.getHotseat().getLayout().getChildrenLayout());
+ childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
}
return childrenLayouts;
}
public Folder getFolderForTag(Object tag) {
- ArrayList<CellLayoutChildren> childrenLayouts = getWorkspaceAndHotseatCellLayoutChildren();
- for (CellLayoutChildren layout: childrenLayouts) {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+ getAllShortcutAndWidgetContainers();
+ for (ShortcutAndWidgetContainer layout: childrenLayouts) {
int count = layout.getChildCount();
for (int i = 0; i < count; i++) {
View child = layout.getChildAt(i);
@@ -3332,8 +3557,9 @@ public class Workspace extends SmoothPagedView
}
public View getViewForTag(Object tag) {
- ArrayList<CellLayoutChildren> childrenLayouts = getWorkspaceAndHotseatCellLayoutChildren();
- for (CellLayoutChildren layout: childrenLayouts) {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+ getAllShortcutAndWidgetContainers();
+ for (ShortcutAndWidgetContainer layout: childrenLayouts) {
int count = layout.getChildCount();
for (int i = 0; i < count; i++) {
View child = layout.getChildAt(i);
@@ -3346,8 +3572,9 @@ public class Workspace extends SmoothPagedView
}
void clearDropTargets() {
- ArrayList<CellLayoutChildren> childrenLayouts = getWorkspaceAndHotseatCellLayoutChildren();
- for (CellLayoutChildren layout: childrenLayouts) {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+ getAllShortcutAndWidgetContainers();
+ for (ShortcutAndWidgetContainer layout: childrenLayouts) {
int childCount = layout.getChildCount();
for (int j = 0; j < childCount; j++) {
View v = layout.getChildAt(j);
@@ -3369,7 +3596,7 @@ public class Workspace extends SmoothPagedView
ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
for (final CellLayout layoutParent: cellLayouts) {
- final ViewGroup layout = layoutParent.getChildrenLayout();
+ final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
// Avoid ANRs by treating each screen separately
post(new Runnable() {
@@ -3455,14 +3682,14 @@ public class Workspace extends SmoothPagedView
}
void updateShortcuts(ArrayList<ApplicationInfo> apps) {
- ArrayList<CellLayoutChildren> childrenLayouts = getWorkspaceAndHotseatCellLayoutChildren();
- for (CellLayoutChildren layout: childrenLayouts) {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
+ for (ShortcutAndWidgetContainer layout: childrenLayouts) {
int childCount = layout.getChildCount();
for (int j = 0; j < childCount; j++) {
final View view = layout.getChildAt(j);
Object tag = view.getTag();
if (tag instanceof ShortcutInfo) {
- ShortcutInfo info = (ShortcutInfo)tag;
+ ShortcutInfo info = (ShortcutInfo) tag;
// We need to check for ACTION_MAIN otherwise getComponent() might
// return null for some shortcuts (for instance, for shortcuts to
// web pages.)
@@ -3474,11 +3701,11 @@ public class Workspace extends SmoothPagedView
for (int k = 0; k < appCount; k++) {
ApplicationInfo app = apps.get(k);
if (app.componentName.equals(name)) {
- info.setIcon(mIconCache.getIcon(info.intent));
- ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null,
- new FastBitmapDrawable(info.getIcon(mIconCache)),
- null, null);
- }
+ BubbleTextView shortcut = (BubbleTextView) view;
+ info.updateIcon(mIconCache);
+ info.title = app.title.toString();
+ shortcut.applyFromShortcutInfo(info, mIconCache);
+ }
}
}
}
@@ -3524,7 +3751,7 @@ public class Workspace extends SmoothPagedView
final ViewGroup parent = (ViewGroup) getParent();
final ImageView qsbDivider = (ImageView) (parent.findViewById(R.id.qsb_divider));
final ImageView dockDivider = (ImageView) (parent.findViewById(R.id.dock_divider));
- final ImageView scrollIndicator = getScrollingIndicator();
+ final View scrollIndicator = getScrollingIndicator();
cancelScrollingIndicatorAnimations();
if (qsbDivider != null) qsbDivider.setAlpha(reducedFade);