From 7d30a37007bac318db1c9af47a9af12d348042a5 Mon Sep 17 00:00:00 2001 From: Adam Cohen Date: Mon, 1 Jul 2013 17:03:59 -0700 Subject: Porting PagedView from KG to gain simplified scaling model, reordering -> We are no longer scaling individual Celllayouts, instead we are scaling the entire Workspace (and the CellLayouts come along for the ride) -> Due to the above change, many assumptions were broken. In particular, our drag and drop / animation archiecture is fairly fragile due to the frequent and complex mapping of points between different bits of the hierarchy. This CL contains a number of fixes which address such breakages. Change-Id: I2e630eab17528729b764b61f587858f6499fd318 --- .../android/launcher3/AppsCustomizePagedView.java | 1 - src/com/android/launcher3/ButtonDropTarget.java | 7 +- src/com/android/launcher3/DragController.java | 21 +- src/com/android/launcher3/DragLayer.java | 92 +- src/com/android/launcher3/DropTarget.java | 21 +- src/com/android/launcher3/Folder.java | 9 +- src/com/android/launcher3/FolderIcon.java | 4 - src/com/android/launcher3/Launcher.java | 3 + src/com/android/launcher3/PagedView.java | 1288 ++++++++++++++++---- src/com/android/launcher3/Workspace.java | 261 +--- 10 files changed, 1185 insertions(+), 522 deletions(-) (limited to 'src') diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java index 9bc1e745a..aa2d531cf 100644 --- a/src/com/android/launcher3/AppsCustomizePagedView.java +++ b/src/com/android/launcher3/AppsCustomizePagedView.java @@ -1696,7 +1696,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen return windowMaxIndex; } - @Override protected String getCurrentPageDescription() { int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; int stringId = R.string.default_scroll_format; diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java index a7486a8e3..3fcc2b904 100644 --- a/src/com/android/launcher3/ButtonDropTarget.java +++ b/src/com/android/launcher3/ButtonDropTarget.java @@ -112,7 +112,7 @@ public class ButtonDropTarget extends TextView implements DropTarget, DragContro } @Override - public void getHitRect(android.graphics.Rect outRect) { + public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) { super.getHitRect(outRect); outRect.bottom += mBottomDragPadding; } @@ -155,11 +155,6 @@ public class ButtonDropTarget extends TextView implements DropTarget, DragContro return to; } - @Override - public DropTarget getDropTargetDelegate(DragObject d) { - return null; - } - public void getLocationInDragLayer(int[] loc) { mLauncher.getDragLayer().getLocationInDragLayer(this, loc); } diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java index 970ce2611..c59f34c25 100644 --- a/src/com/android/launcher3/DragController.java +++ b/src/com/android/launcher3/DragController.java @@ -509,10 +509,6 @@ public class DragController { private void checkTouchMove(DropTarget dropTarget) { if (dropTarget != null) { - DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject); - if (delegate != null) { - dropTarget = delegate; - } if (mLastDropTarget != dropTarget) { if (mLastDropTarget != null) { mLastDropTarget.onDragExit(mDragObject); @@ -701,24 +697,15 @@ public class DragController { if (!target.isDropEnabled()) continue; - target.getHitRect(r); - - // Convert the hit rect to DragLayer coordinates - target.getLocationInDragLayer(dropCoordinates); - r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop()); + target.getHitRectRelativeToDragLayer(r); mDragObject.x = x; mDragObject.y = y; if (r.contains(x, y)) { - DropTarget delegate = target.getDropTargetDelegate(mDragObject); - if (delegate != null) { - target = delegate; - target.getLocationInDragLayer(dropCoordinates); - } - // Make dropCoordinates relative to the DropTarget - dropCoordinates[0] = x - dropCoordinates[0]; - dropCoordinates[1] = y - dropCoordinates[1]; + dropCoordinates[0] = x; + dropCoordinates[1] = y; + mLauncher.getDragLayer().mapCoordInSelfToDescendent((View) target, dropCoordinates); return target; } diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java index b02f803d6..11539e99d 100644 --- a/src/com/android/launcher3/DragLayer.java +++ b/src/com/android/launcher3/DragLayer.java @@ -24,6 +24,7 @@ import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; @@ -295,8 +296,10 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang mTmpXY[0] = 0; mTmpXY[1] = 0; float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY); + r.set(mTmpXY[0], mTmpXY[1], - mTmpXY[0] + descendant.getWidth(), mTmpXY[1] + descendant.getHeight()); + (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()), + (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight())); return scale; } @@ -306,32 +309,93 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang return getDescendantCoordRelativeToSelf(child, loc); } + public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) { + return getDescendantCoordRelativeToSelf(descendant, coord, false); + } + /** * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's * coordinates. * * @param descendant The descendant to which the passed coordinate is relative. * @param coord The coordinate that we want mapped. + * @param includeRootScroll Whether or not to account for the scroll of the root descendant: + * sometimes this is relevant as in a child's coordinates within the root descendant. * @return The factor by which this descendant is scaled relative to this DragLayer. Caution * this scale factor is assumed to be equal in X and Y, and so if at any point this * assumption fails, we will need to return a pair of scale factors. */ - public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) { + public float getDescendantCoordRelativeToSelf(View descendant, int[] coord, + boolean includeRootScroll) { + ArrayList ancestorChain = new ArrayList(); + + float[] pt = {coord[0], coord[1]}; + + View v = descendant; + while(v != this && v != null) { + ancestorChain.add(v); + v = (View) v.getParent(); + } + ancestorChain.add(this); + float scale = 1.0f; + int count = ancestorChain.size(); + for (int i = 0; i < count; i++) { + View v0 = ancestorChain.get(i); + View v1 = i < count -1 ? ancestorChain.get(i + 1) : null; + + // For TextViews, scroll has a meaning which relates to the text position + // which is very strange... ignore the scroll. + if (v0 != descendant || includeRootScroll) { + pt[0] -= v0.getScrollX(); + pt[1] -= v0.getScrollY(); + } + + v0.getMatrix().mapPoints(pt); + pt[0] += v0.getLeft(); + pt[1] += v0.getTop(); + scale *= v0.getScaleX(); + } + + coord[0] = (int) Math.round(pt[0]); + coord[1] = (int) Math.round(pt[1]); + return scale; + } + + /** + * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}. + */ + public float mapCoordInSelfToDescendent(View descendant, int[] coord) { + ArrayList ancestorChain = new ArrayList(); + float[] pt = {coord[0], coord[1]}; - descendant.getMatrix().mapPoints(pt); - scale *= descendant.getScaleX(); - pt[0] += descendant.getLeft(); - pt[1] += descendant.getTop(); - ViewParent viewParent = descendant.getParent(); - while (viewParent instanceof View && viewParent != this) { - final View view = (View)viewParent; - view.getMatrix().mapPoints(pt); - scale *= view.getScaleX(); - pt[0] += view.getLeft() - view.getScrollX(); - pt[1] += view.getTop() - view.getScrollY(); - viewParent = view.getParent(); + + View v = descendant; + while(v != this) { + ancestorChain.add(v); + v = (View) v.getParent(); } + ancestorChain.add(this); + + float scale = 1.0f; + Matrix inverse = new Matrix(); + int count = ancestorChain.size(); + for (int i = count - 1; i >= 0; i--) { + View ancestor = ancestorChain.get(i); + View next = i > 0 ? ancestorChain.get(i-1) : null; + + pt[0] += ancestor.getScrollX(); + pt[1] += ancestor.getScrollY(); + + if (next != null) { + pt[0] -= next.getLeft(); + pt[1] -= next.getTop(); + next.getMatrix().invert(inverse); + inverse.mapPoints(pt); + scale *= next.getScaleX(); + } + } + coord[0] = (int) Math.round(pt[0]); coord[1] = (int) Math.round(pt[1]); return scale; diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index fa364fadf..64f0ac867 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -140,25 +140,6 @@ public interface DropTarget { */ 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. - * - * @param source DragSource where the drag started - * @param x X coordinate of the drop location - * @param y Y coordinate of the drop location - * @param xOffset Horizontal offset with the object being dragged where the original - * touch happened - * @param yOffset Vertical offset with the object being dragged where the original - * touch happened - * @param dragView The DragView that's being dragged around on screen. - * @param dragInfo Data associated with the object being dragged - * - * @return The DropTarget to delegate to, or null to not delegate to another object. - */ - DropTarget getDropTargetDelegate(DragObject dragObject); - /** * Check if a drop action can occur at, or near, the requested location. * This will be called just before onDrop. @@ -177,7 +158,7 @@ public interface DropTarget { boolean acceptDrop(DragObject dragObject); // These methods are implemented in Views - void getHitRect(Rect outRect); + void getHitRectRelativeToDragLayer(Rect outRect); void getLocationInDragLayer(int[] loc); int getLeft(); int getTop(); diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java index cf1a43225..4de929725 100644 --- a/src/com/android/launcher3/Folder.java +++ b/src/com/android/launcher3/Folder.java @@ -832,10 +832,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList return true; } - public DropTarget getDropTargetDelegate(DragObject d) { - return null; - } - private void setupContentDimensions(int count) { ArrayList list = getItemsInReadingOrder(); @@ -1208,4 +1204,9 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList startEditingFolderName(); } } + + @Override + public void getHitRectRelativeToDragLayer(Rect outRect) { + getHitRect(outRect); + } } diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java index e11d7d18a..92f126c3f 100644 --- a/src/com/android/launcher3/FolderIcon.java +++ b/src/com/android/launcher3/FolderIcon.java @@ -427,10 +427,6 @@ public class FolderIcon extends LinearLayout implements FolderListener { onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d); } - public DropTarget getDropTargetDelegate(DragObject d) { - return null; - } - private void computePreviewDrawingParams(int drawableSize, int totalSize) { if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize) { mIntrinsicIconSize = drawableSize; diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 47fa66b77..b8fce6def 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -2435,6 +2435,9 @@ public class Launcher extends Activity // User long pressed on empty space mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + // Disabling reordering until we sort out some issues. + //mWorkspace.startReordering(); + // TODO: need to have a new way to set wallpaper or start reordering startWallpaper(); } else { if (!(itemUnderLongClick instanceof Folder)) { diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 842dc2034..20d7e5e00 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * 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. @@ -18,15 +18,23 @@ package com.android.launcher3; import android.animation.Animator; 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.content.Context; +import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.PointF; import android.graphics.Rect; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.util.Log; import android.view.InputDevice; import android.view.KeyEvent; @@ -39,11 +47,12 @@ import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.animation.AnimationUtils; +import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; import android.widget.Scroller; -import com.android.launcher3.R; - import java.util.ArrayList; /** @@ -58,8 +67,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // the min drag distance for a fling to register, to prevent random page shifts private static final int MIN_LENGTH_FOR_FLING = 25; - protected static final int PAGE_SNAP_ANIMATION_DURATION = 550; - protected static final int MAX_PAGE_SNAP_DURATION = 750; + protected static final int PAGE_SNAP_ANIMATION_DURATION = 750; protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950; protected static final float NANOTIME_DIV = 1000000000.0f; @@ -76,6 +84,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private static final int MIN_SNAP_VELOCITY = 1500; private static final int MIN_FLING_VELOCITY = 250; + // We are disabling touch interaction of the widget region for factory ROM. + private static final boolean DISABLE_TOUCH_INTERACTION = false; + private static final boolean DISABLE_TOUCH_SIDE_PAGES = false; + private static final boolean DISABLE_FLING_TO_DELETE = false; + static final int AUTOMATIC_PAGE_SPACING = -1; protected int mFlingThresholdVelocity; @@ -96,7 +109,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected Scroller mScroller; private VelocityTracker mVelocityTracker; + private float mParentDownMotionX; + private float mParentDownMotionY; private float mDownMotionX; + private float mDownMotionY; + private float mDownScrollX; protected float mLastMotionX; protected float mLastMotionXRemainder; protected float mLastMotionY; @@ -110,6 +127,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected final static int TOUCH_STATE_SCROLLING = 1; protected final static int TOUCH_STATE_PREV_PAGE = 2; protected final static int TOUCH_STATE_NEXT_PAGE = 3; + protected final static int TOUCH_STATE_REORDERING = 4; + protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f; protected int mTouchState = TOUCH_STATE_REST; @@ -117,8 +136,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected OnLongClickListener mLongClickListener; - protected boolean mAllowLongPress = true; - protected int mTouchSlop; private int mPagingTouchSlop; private int mMaximumVelocity; @@ -158,7 +175,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected boolean mContentIsRefreshable = true; // If true, modify alpha of neighboring pages as user scrolls left/right - protected boolean mFadeInAdjacentScreens = true; + protected boolean mFadeInAdjacentScreens = false; // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding // to switch to a new page @@ -173,6 +190,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // All syncs and layout passes are deferred until data is ready. protected boolean mIsDataReady = false; + protected boolean mAllowLongPress = true; + // Scrolling indicator private ValueAnimator mScrollIndicatorAnimator; private View mScrollIndicator; @@ -186,8 +205,64 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected static final int sScrollIndicatorFlashDuration = 650; private boolean mScrollingPaused = false; - // If set, will defer loading associated pages until the scrolling settles - private boolean mDeferLoadAssociatedPagesUntilScrollCompletes; + // The viewport whether the pages are to be contained (the actual view may be larger than the + // viewport) + private Rect mViewport = new Rect(); + + // Reordering + // We use the min scale to determine how much to expand the actually PagedView measured + // dimensions such that when we are zoomed out, the view is not clipped + private int REORDERING_DROP_REPOSITION_DURATION = 200; + protected int REORDERING_REORDER_REPOSITION_DURATION = 300; + protected int REORDERING_ZOOM_IN_OUT_DURATION = 250; + private int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 300; + private float REORDERING_SIDE_PAGE_BUFFER_PERCENTAGE = 0.1f; + private long REORDERING_DELETE_DROP_TARGET_FADE_DURATION = 150; + private float mMinScale = 1f; + protected View mDragView; + protected AnimatorSet mZoomInOutAnim; + private Runnable mSidePageHoverRunnable; + private int mSidePageHoverIndex = -1; + // This variable's scope is only for the duration of startReordering() and endReordering() + private boolean mReorderingStarted = false; + // This variable's scope is for the duration of startReordering() and after the zoomIn() + // animation after endReordering() + private boolean mIsReordering; + // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition + private int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2; + private int mPostReorderingPreZoomInRemainingAnimationCount; + private Runnable mPostReorderingPreZoomInRunnable; + + // Edge swiping + private boolean mOnlyAllowEdgeSwipes = false; + private boolean mDownEventOnEdge = false; + private int mEdgeSwipeRegionSize = 0; + + // Convenience/caching + private Matrix mTmpInvMatrix = new Matrix(); + private float[] mTmpPoint = new float[2]; + private Rect mTmpRect = new Rect(); + private Rect mAltTmpRect = new Rect(); + + // Fling to delete + private int FLING_TO_DELETE_FADE_OUT_DURATION = 350; + private float FLING_TO_DELETE_FRICTION = 0.035f; + // The degrees specifies how much deviation from the up vector to still consider a fling "up" + private float FLING_TO_DELETE_MAX_FLING_DEGREES = 65f; + protected int mFlingToDeleteThresholdVelocity = -1400; + // Drag to delete + private boolean mDeferringForDelete = false; + private int DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250; + private int DRAG_TO_DELETE_FADE_OUT_DURATION = 350; + + // Drop to delete + private View mDeleteDropTarget; + + private boolean mAutoComputePageSpacing = false; + private boolean mRecomputePageSpacing = false; + + // Bouncer + private boolean mTopAlignPageWhenShrinkingForBouncer = false; public interface PageSwitchListener { void onPageSwitch(View newPage, int newPageIndex); @@ -203,10 +278,12 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc public PagedView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedView, defStyle, 0); setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0)); + if (mPageSpacing < 0) { + mAutoComputePageSpacing = mRecomputePageSpacing = true; + } mPageLayoutPaddingTop = a.getDimensionPixelSize( R.styleable.PagedView_pageLayoutPaddingTop, 0); mPageLayoutPaddingBottom = a.getDimensionPixelSize( @@ -240,17 +317,86 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mCenterPagesVertically = true; final ViewConfiguration configuration = ViewConfiguration.get(getContext()); - mTouchSlop = configuration.getScaledTouchSlop(); + mTouchSlop = configuration.getScaledPagingTouchSlop(); mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mDensity = getResources().getDisplayMetrics().density; + // Scale the fling-to-delete threshold by the density + mFlingToDeleteThresholdVelocity = + (int) (mFlingToDeleteThresholdVelocity * mDensity); + mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity); mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity); mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity); setOnHierarchyChangeListener(this); } + void setDeleteDropTarget(View v) { + mDeleteDropTarget = v; + } + + // Convenience methods to map points from self to parent and vice versa + float[] mapPointFromViewToParent(View v, float x, float y) { + mTmpPoint[0] = x; + mTmpPoint[1] = y; + v.getMatrix().mapPoints(mTmpPoint); + mTmpPoint[0] += v.getLeft(); + mTmpPoint[1] += v.getTop(); + return mTmpPoint; + } + float[] mapPointFromParentToView(View v, float x, float y) { + mTmpPoint[0] = x - v.getLeft(); + mTmpPoint[1] = y - v.getTop(); + v.getMatrix().invert(mTmpInvMatrix); + mTmpInvMatrix.mapPoints(mTmpPoint); + return mTmpPoint; + } + + void updateDragViewTranslationDuringDrag() { + float x = mLastMotionX - mDownMotionX + getScrollX() - mDownScrollX; + float y = mLastMotionY - mDownMotionY; + mDragView.setTranslationX(x); + mDragView.setTranslationY(y); + + if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): " + x + ", " + y); + } + + public void setMinScale(float f) { + mMinScale = f; + requestLayout(); + } + + @Override + public void setScaleX(float scaleX) { + super.setScaleX(scaleX); + if (isReordering(true)) { + float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY); + mLastMotionX = p[0]; + mLastMotionY = p[1]; + updateDragViewTranslationDuringDrag(); + } + } + + // Convenience methods to get the actual width/height of the PagedView (since it is measured + // to be larger to account for the minimum possible scale) + int getViewportWidth() { + return mViewport.width(); + } + int getViewportHeight() { + return mViewport.height(); + } + + // Convenience methods to get the offset ASSUMING that we are centering the pages in the + // PagedView both horizontally and vertically + int getViewportOffsetX() { + return (getMeasuredWidth() - getViewportWidth()) / 2; + } + + int getViewportOffsetY() { + return (getMeasuredHeight() - getViewportHeight()) / 2; + } + public void setPageSwitchListener(PageSwitchListener pageSwitchListener) { mPageSwitchListener = pageSwitchListener; if (mPageSwitchListener != null) { @@ -272,6 +418,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected void setDataIsReady() { mIsDataReady = true; } + protected boolean isDataReady() { return mIsDataReady; } @@ -284,6 +431,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc int getCurrentPage() { return mCurrentPage; } + int getNextPage() { return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; } @@ -306,13 +454,9 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc * the previous tab page. */ protected void updateCurrentPageScroll() { - // If the current page is invalid, just reset the scroll position to zero - int newX = 0; - if (0 <= mCurrentPage && mCurrentPage < getPageCount()) { - int offset = getChildOffset(mCurrentPage); - int relOffset = getRelativeChildOffset(mCurrentPage); - newX = offset - relOffset; - } + int offset = getChildOffset(mCurrentPage); + int relOffset = getRelativeChildOffset(mCurrentPage); + int newX = offset - relOffset; scrollTo(newX, 0); mScroller.setFinalX(newX); mScroller.forceFinished(true); @@ -362,7 +506,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); } } - protected void pageBeginMoving() { if (!mIsPageMoving) { mIsPageMoving = true; @@ -410,28 +553,17 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc @Override public void scrollTo(int x, int y) { - final boolean isRtl = isLayoutRtl(); mUnboundedScrollX = x; - boolean isXBeforeFirstPage = isRtl ? (x > mMaxScrollX) : (x < 0); - boolean isXAfterLastPage = isRtl ? (x < 0) : (x > mMaxScrollX); - if (isXBeforeFirstPage) { + if (x < 0) { super.scrollTo(0, y); if (mAllowOverScroll) { - if (isRtl) { - overScroll(x - mMaxScrollX); - } else { - overScroll(x); - } + overScroll(x); } - } else if (isXAfterLastPage) { + } else if (x > mMaxScrollX) { super.scrollTo(mMaxScrollX, y); if (mAllowOverScroll) { - if (isRtl) { - overScroll(x); - } else { - overScroll(x - mMaxScrollX); - } + overScroll(x - mMaxScrollX); } } else { mOverScrollX = x; @@ -440,6 +572,14 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mTouchX = x; mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + + // Update the last motion events when scrolling + if (isReordering(true)) { + float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY); + mLastMotionX = p[0]; + mLastMotionY = p[1]; + updateDragViewTranslationDuringDrag(); + } } // we moved this functionality to a helper function so SmoothPagedView can reuse it @@ -458,27 +598,13 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mNextPage = INVALID_PAGE; notifyPageSwitchListener(); - // Load the associated pages if necessary - if (mDeferLoadAssociatedPagesUntilScrollCompletes) { - loadAssociatedPages(mCurrentPage); - mDeferLoadAssociatedPagesUntilScrollCompletes = false; - } - // We don't want to trigger a page end moving unless the page has settled // and the user has stopped scrolling if (mTouchState == TOUCH_STATE_REST) { pageEndMoving(); } - // Notify the user when the page changes - AccessibilityManager accessibilityManager = (AccessibilityManager) - getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - if (accessibilityManager.isEnabled()) { - AccessibilityEvent ev = - AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); - ev.getText().add(getCurrentPageDescription()); - sendAccessibilityEventUnchecked(ev); - } + onPostReorderingAnimationCompleted(); return true; } return false; @@ -489,6 +615,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc computeScrollHelper(); } + protected boolean shouldSetTopAlignedPivotForWidget(int childIndex) { + return mTopAlignPageWhenShrinkingForBouncer; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (!mIsDataReady || getChildCount() == 0) { @@ -496,12 +626,26 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc return; } - final int widthMode = MeasureSpec.getMode(widthMeasureSpec); - final int widthSize = MeasureSpec.getSize(widthMeasureSpec); - final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + // We measure the dimensions of the PagedView to be larger than the pages so that when we + // zoom out (and scale down), the view is still contained in the parent + View parent = (View) getParent(); + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); - if (widthMode != MeasureSpec.EXACTLY) { - throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); + // NOTE: We multiply by 1.5f to account for the fact that depending on the offset of the + // viewport, we can be at most one and a half screens offset once we scale down + DisplayMetrics dm = getResources().getDisplayMetrics(); + int maxSize = Math.max(dm.widthPixels, dm.heightPixels); + int parentWidthSize = (int) (1.5f * maxSize); + int parentHeightSize = maxSize; + int scaledWidthSize = (int) (parentWidthSize / mMinScale); + int scaledHeightSize = (int) (parentHeightSize / mMinScale); + mViewport.set(0, 0, widthSize, heightSize); + + if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; } // Return early if we aren't given a proper dimension @@ -515,15 +659,16 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect * each page to have the same width. */ - int maxChildHeight = 0; - final int verticalPadding = getPaddingTop() + getPaddingBottom(); final int horizontalPadding = getPaddingLeft() + getPaddingRight(); - // The children are given the same width and height as the workspace // unless they were set to WRAP_CONTENT if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize); + if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize); + if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize); + if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding); + if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding); final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { // disallowing padding in paged view (just pass 0) @@ -550,33 +695,25 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight()); - if (DEBUG) Log.d(TAG, "\tmeasure-child" + i + ": " + child.getMeasuredWidth() + ", " - + child.getMeasuredHeight()); - } - - if (heightMode == MeasureSpec.AT_MOST) { - heightSize = maxChildHeight + verticalPadding; } - - setMeasuredDimension(widthSize, heightSize); + setMeasuredDimension(scaledWidthSize, scaledHeightSize); // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions. // We also wait until we set the measured dimensions before flushing the cache as well, to // ensure that the cache is filled with good values. invalidateCachedOffsets(); - if (mChildCountOnLastMeasure != getChildCount()) { + if (mChildCountOnLastMeasure != getChildCount() && !mDeferringForDelete) { setCurrentPage(mCurrentPage); } mChildCountOnLastMeasure = getChildCount(); if (childCount > 0) { - if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMeasuredWidth() + ", " + if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getViewportWidth() + ", " + getChildWidth(0)); // Calculate the variable page spacing if necessary - if (mPageSpacing == AUTOMATIC_PAGE_SPACING) { + if (mAutoComputePageSpacing && mRecomputePageSpacing) { // 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 @@ -585,64 +722,19 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc int spacing = Math.max(offset, widthSize - offset - getChildAt(0).getMeasuredWidth()); setPageSpacing(spacing); + mRecomputePageSpacing = false; } } updateScrollingIndicatorPosition(); if (childCount > 0) { - final int index = isLayoutRtl() ? 0 : childCount - 1; - mMaxScrollX = getChildOffset(index) - getRelativeChildOffset(index); + mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1); } else { mMaxScrollX = 0; } } - protected void scrollToNewPageWithoutMovingPages(int newCurrentPage) { - int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage); - int delta = newX - getScrollX(); - - final int pageCount = getChildCount(); - for (int i = 0; i < pageCount; i++) { - View page = (View) getPageAt(i); - page.setX(page.getX() + delta); - } - setCurrentPage(newCurrentPage); - } - - // A layout scale of 1.0f assumes that the pages, in their unshrunken state, have a - // scale of 1.0f. A layout scale of 0.8f assumes the pages have a scale of 0.8f, and - // tightens the layout accordingly - public void setLayoutScale(float childrenScale) { - mLayoutScale = childrenScale; - invalidateCachedOffsets(); - - // Now we need to do a re-layout, but preserving absolute X and Y coordinates - int childCount = getChildCount(); - float childrenX[] = new float[childCount]; - float childrenY[] = new float[childCount]; - for (int i = 0; i < childCount; i++) { - final View child = getPageAt(i); - childrenX[i] = child.getX(); - childrenY[i] = child.getY(); - } - // Trigger a full re-layout (never just call onLayout directly!) - int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY); - int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY); - requestLayout(); - measure(widthSpec, heightSpec); - layout(getLeft(), getTop(), getRight(), getBottom()); - for (int i = 0; i < childCount; i++) { - final View child = getPageAt(i); - child.setX(childrenX[i]); - child.setY(childrenY[i]); - } - - // Also, the page offset has changed (since the pages are now smaller); - // update the page offset, but again preserving absolute X and Y coordinates - scrollToNewPageWithoutMovingPages(mCurrentPage); - } - public void setPageSpacing(int pageSpacing) { mPageSpacing = pageSpacing; invalidateCachedOffsets(); @@ -655,23 +747,25 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } if (DEBUG) Log.d(TAG, "PagedView.onLayout()"); - final int verticalPadding = getPaddingTop() + getPaddingBottom(); final int childCount = getChildCount(); - final boolean isRtl = isLayoutRtl(); - final int startIndex = isRtl ? childCount - 1 : 0; - final int endIndex = isRtl ? -1 : childCount; - final int delta = isRtl ? -1 : 1; - int childLeft = getRelativeChildOffset(startIndex); - for (int i = startIndex; i != endIndex; i += delta) { + int offsetX = getViewportOffsetX(); + int offsetY = getViewportOffsetY(); + + // Update the viewport offsets + mViewport.offset(offsetX, offsetY); + + int verticalPadding = getPaddingTop() + getPaddingBottom(); + int childLeft = offsetX + getRelativeChildOffset(0); + for (int i = 0; i < childCount; i++) { final View child = getPageAt(i); + int childTop = offsetY + getPaddingTop(); + if (mCenterPagesVertically) { + childTop += ((getViewportHeight() - verticalPadding) - child.getMeasuredHeight()) / 2; + } if (child.getVisibility() != View.GONE) { final int childWidth = getScaledMeasuredWidth(child); final int childHeight = child.getMeasuredHeight(); - int childTop = getPaddingTop(); - if (mCenterPagesVertically) { - childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2; - } if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop); child.layout(childLeft, childTop, @@ -712,12 +806,16 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // This ensures that when children are added, they get the correct transforms / alphas // in accordance with any scroll effects. mForceScreenScrolled = true; + mRecomputePageSpacing = true; invalidate(); invalidateCachedOffsets(); } @Override public void onChildViewRemoved(View parent, View child) { + mForceScreenScrolled = true; + invalidate(); + invalidateCachedOffsets(); } protected void invalidateCachedOffsets() { @@ -740,7 +838,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } protected int getChildOffset(int index) { - final boolean isRtl = isLayoutRtl(); + if (index < 0 || index > getChildCount() - 1) return 0; + int[] childOffsets = Float.compare(mLayoutScale, 1f) == 0 ? mChildOffsets : mChildOffsetsWithLayoutScale; @@ -750,11 +849,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (getChildCount() == 0) return 0; - final int startIndex = isRtl ? getChildCount() - 1 : 0; - final int endIndex = isRtl ? index : index; - final int delta = isRtl ? -1 : 1; - int offset = getRelativeChildOffset(startIndex); - for (int i = startIndex; i != endIndex; i += delta) { + int offset = getRelativeChildOffset(0); + for (int i = 0; i < index; ++i) { offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing; } if (childOffsets != null) { @@ -765,12 +861,14 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } protected int getRelativeChildOffset(int index) { + if (index < 0 || index > getChildCount() - 1) return 0; + if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) { return mChildRelativeOffsets[index]; } else { final int padding = getPaddingLeft() + getPaddingRight(); final int offset = getPaddingLeft() + - (getMeasuredWidth() - padding - getChildWidth(index)) / 2; + (getViewportWidth() - padding - getChildWidth(index)) / 2; if (mChildRelativeOffsets != null) { mChildRelativeOffsets[index] = offset; } @@ -787,36 +885,47 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc return (int) (maxWidth * mLayoutScale + 0.5f); } + void boundByReorderablePages(boolean isReordering, int[] range) { + // Do nothing + } + + // TODO: Fix this protected void getVisiblePages(int[] range) { - final boolean isRtl = isLayoutRtl(); + range[0] = 0; + range[1] = getPageCount() - 1; + + /* final int pageCount = getChildCount(); if (pageCount > 0) { - final int screenWidth = getMeasuredWidth(); - int leftScreen = isRtl ? pageCount - 1 : 0; + final int screenWidth = getViewportWidth(); + int leftScreen = 0; int rightScreen = 0; - int endIndex = isRtl ? 0 : pageCount - 1; - int delta = isRtl ? -1 : 1; + int offsetX = getViewportOffsetX() + getScrollX(); View currPage = getPageAt(leftScreen); - while (leftScreen != endIndex && + while (leftScreen < pageCount - 1 && currPage.getX() + currPage.getWidth() - - currPage.getPaddingRight() < getScrollX()) { - leftScreen += delta; + currPage.getPaddingRight() < offsetX) { + leftScreen++; currPage = getPageAt(leftScreen); } rightScreen = leftScreen; - currPage = getPageAt(rightScreen + delta); - while (rightScreen != endIndex && - currPage.getX() - currPage.getPaddingLeft() < getScrollX() + screenWidth) { - rightScreen += delta; - currPage = getPageAt(rightScreen + delta); + currPage = getPageAt(rightScreen + 1); + while (rightScreen < pageCount - 1 && + currPage.getX() - currPage.getPaddingLeft() < offsetX + screenWidth) { + rightScreen++; + currPage = getPageAt(rightScreen + 1); } - range[0] = Math.min(leftScreen, rightScreen); - range[1] = Math.max(leftScreen, rightScreen); + + // TEMP: this is a hacky way to ensure that animations to new pages are not clipped + // because we don't draw them while scrolling? + range[0] = Math.max(0, leftScreen - 1); + range[1] = Math.min(rightScreen + 1, getChildCount() - 1); } else { range[0] = -1; range[1] = -1; } + */ } protected boolean shouldDrawChild(View child) { @@ -825,7 +934,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc @Override protected void dispatchDraw(Canvas canvas) { - int halfScreenSize = getMeasuredWidth() / 2; + int halfScreenSize = getViewportWidth() / 2; // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. // Otherwise it is equal to the scaled overscroll position. int screenCenter = mOverScrollX + halfScreenSize; @@ -851,13 +960,20 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(), getScrollY() + getBottom() - getTop()); - for (int i = getChildCount() - 1; i >= 0; i--) { + // Draw all the children, leaving the drag view for last + for (int i = pageCount - 1; i >= 0; i--) { final View v = getPageAt(i); + if (v == mDragView) continue; if (mForceDrawAllChildrenNextFrame || (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) { drawChild(canvas, v, drawingTime); } } + // Draw the drag view on top (if there is one) + if (mDragView != null) { + drawChild(canvas, mDragView, drawingTime); + } + mForceDrawAllChildrenNextFrame = false; canvas.restore(); } @@ -891,7 +1007,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc @Override public boolean dispatchUnhandledMove(View focused, int direction) { - // XXX-RTL: This will be fixed in a future CL if (direction == View.FOCUS_LEFT) { if (getCurrentPage() > 0) { snapToPage(getCurrentPage() - 1); @@ -908,7 +1023,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc @Override public void addFocusables(ArrayList views, int direction, int focusableMode) { - // XXX-RTL: This will be fixed in a future CL if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) { getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode); } @@ -969,27 +1083,40 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc * Return true if a tap at (x, y) should trigger a flip to the previous page. */ protected boolean hitsPreviousPage(float x, float y) { - if (isLayoutRtl()) { - return (x > (getMeasuredWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing)); - } else { - return (x < getRelativeChildOffset(mCurrentPage) - mPageSpacing); - } + return (x < getViewportOffsetX() + getRelativeChildOffset(mCurrentPage) - mPageSpacing); } /** * Return true if a tap at (x, y) should trigger a flip to the next page. */ protected boolean hitsNextPage(float x, float y) { - if (isLayoutRtl()) { - return (x < getRelativeChildOffset(mCurrentPage) - mPageSpacing); - } else { - return (x > (getMeasuredWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing)); - } + return (x > (getViewportOffsetX() + getViewportWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing)); + } + /** Returns whether x and y originated within the buffered viewport */ + private boolean isTouchPointInViewportWithBuffer(int x, int y) { + mTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top, + mViewport.right + mViewport.width() / 2, mViewport.bottom); + return mTmpRect.contains(x, y); + } + + /** Returns whether x and y originated within the current page view bounds */ + private boolean isTouchPointInCurrentPage(int x, int y) { + View v = getPageAt(getCurrentPage()); + if (v != null) { + mTmpRect.set((v.getLeft() - getScrollX()), 0, (v.getRight() - getScrollX()), + v.getBottom()); + return mTmpRect.contains(x, y); + } + return false; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { + if (DISABLE_TOUCH_INTERACTION) { + return false; + } + /* * This method JUST determines whether we want to intercept the motion. * If we return true, onTouchEvent will be called and we do the actual @@ -1033,12 +1160,23 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc final float y = ev.getY(); // Remember location of down touch mDownMotionX = x; + mDownMotionY = y; + mDownScrollX = getScrollX(); mLastMotionX = x; mLastMotionY = y; + float[] p = mapPointFromViewToParent(this, x, y); + mParentDownMotionX = p[0]; + mParentDownMotionY = p[1]; mLastMotionXRemainder = 0; mTotalMotionX = 0; mActivePointerId = ev.getPointerId(0); - mAllowLongPress = true; + + // Determine if the down event is within the threshold to be an edge swipe + int leftEdgeBoundary = getViewportOffsetX() + mEdgeSwipeRegionSize; + int rightEdgeBoundary = getMeasuredWidth() - getViewportOffsetX() - mEdgeSwipeRegionSize; + if ((mDownMotionX <= leftEdgeBoundary || mDownMotionX >= rightEdgeBoundary)) { + mDownEventOnEdge = true; + } /* * If being flinged and user touches the screen, initiate drag; @@ -1051,17 +1189,23 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mTouchState = TOUCH_STATE_REST; mScroller.abortAnimation(); } else { - mTouchState = TOUCH_STATE_SCROLLING; + if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) { + mTouchState = TOUCH_STATE_SCROLLING; + } else { + mTouchState = TOUCH_STATE_REST; + } } // check if this can be the beginning of a tap on the side of the pages // to scroll the current page - if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) { - if (getChildCount() > 0) { - if (hitsPreviousPage(x, y)) { - mTouchState = TOUCH_STATE_PREV_PAGE; - } else if (hitsNextPage(x, y)) { - mTouchState = TOUCH_STATE_NEXT_PAGE; + if (!DISABLE_TOUCH_SIDE_PAGES) { + if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) { + if (getChildCount() > 0) { + if (hitsPreviousPage(x, y)) { + mTouchState = TOUCH_STATE_PREV_PAGE; + } else if (hitsNextPage(x, y)) { + mTouchState = TOUCH_STATE_NEXT_PAGE; + } } } } @@ -1070,10 +1214,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - mTouchState = TOUCH_STATE_REST; - mAllowLongPress = false; - mActivePointerId = INVALID_POINTER; - releaseVelocityTracker(); + resetTouchState(); + // Just intercept the touch event on up if we tap outside the strict viewport + if (!isTouchPointInCurrentPage((int) mLastMotionX, (int) mLastMotionY)) { + return true; + } break; case MotionEvent.ACTION_POINTER_UP: @@ -1098,16 +1243,19 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc * user moves their touch point too far. */ protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) { - /* - * Locally do absolute value. mLastMotionX is set to the y value - * of the down event. - */ + // Disallow scrolling if we don't have a valid pointer index final int pointerIndex = ev.findPointerIndex(mActivePointerId); - if (pointerIndex == -1) { - return; - } + if (pointerIndex == -1) return; + + // Disallow scrolling if we started the gesture from outside the viewport final float x = ev.getX(pointerIndex); final float y = ev.getY(pointerIndex); + if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return; + + // If we're only allowing edge swipes, we break out early if the down event wasn't + // at the edge. + if (mOnlyAllowEdgeSwipes && !mDownEventOnEdge) return; + final int xDiff = (int) Math.abs(x - mLastMotionX); final int yDiff = (int) Math.abs(y - mLastMotionY); @@ -1123,18 +1271,20 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mTotalMotionX += Math.abs(mLastMotionX - x); mLastMotionX = x; mLastMotionXRemainder = 0; - mTouchX = getScrollX(); + mTouchX = getViewportOffsetX() + getScrollX(); mSmoothingTime = System.nanoTime() / NANOTIME_DIV; pageBeginMoving(); } - // Either way, cancel any pending longpress - cancelCurrentPageLongPress(); } } + protected float getMaxScrollProgress() { + return 1.0f; + } + protected void cancelCurrentPageLongPress() { if (mAllowLongPress) { - mAllowLongPress = false; + //mAllowLongPress = false; // Try canceling the long press. It could also have been scheduled // by a distant descendant, so use the mAllowLongPress flag to block // everything @@ -1145,16 +1295,25 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } } + protected float getBoundedScrollProgress(int screenCenter, View v, int page) { + final int halfScreenSize = getViewportWidth() / 2; + + screenCenter = Math.min(getScrollX() + halfScreenSize, screenCenter); + screenCenter = Math.max(halfScreenSize, screenCenter); + + return getScrollProgress(screenCenter, v, page); + } + protected float getScrollProgress(int screenCenter, View v, int page) { - final int halfScreenSize = getMeasuredWidth() / 2; + final int halfScreenSize = getViewportWidth() / 2; int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing; int delta = screenCenter - (getChildOffset(page) - getRelativeChildOffset(page) + halfScreenSize); float scrollProgress = delta / (totalDistance * 1.0f); - scrollProgress = Math.min(scrollProgress, 1.0f); - scrollProgress = Math.max(scrollProgress, -1.0f); + scrollProgress = Math.min(scrollProgress, getMaxScrollProgress()); + scrollProgress = Math.max(scrollProgress, - getMaxScrollProgress()); return scrollProgress; } @@ -1166,7 +1325,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } protected void acceleratedOverScroll(float amount) { - int screenSize = getMeasuredWidth(); + int screenSize = getViewportWidth(); // We want to reach the max over scroll effect when the user has // over scrolled half the size of the screen @@ -1191,7 +1350,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } protected void dampedOverScroll(float amount) { - int screenSize = getMeasuredWidth(); + int screenSize = getViewportWidth(); float f = (amount / screenSize); @@ -1228,6 +1387,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc @Override public boolean onTouchEvent(MotionEvent ev) { + if (DISABLE_TOUCH_INTERACTION) { + return false; + } + // Skip touch handling if there are no pages to swipe if (getChildCount() <= 0) return super.onTouchEvent(ev); @@ -1247,9 +1410,22 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // Remember where the motion event started mDownMotionX = mLastMotionX = ev.getX(); + mDownMotionY = mLastMotionY = ev.getY(); + mDownScrollX = getScrollX(); + float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY); + mParentDownMotionX = p[0]; + mParentDownMotionY = p[1]; mLastMotionXRemainder = 0; mTotalMotionX = 0; mActivePointerId = ev.getPointerId(0); + + // Determine if the down event is within the threshold to be an edge swipe + int leftEdgeBoundary = getViewportOffsetX() + mEdgeSwipeRegionSize; + int rightEdgeBoundary = getMeasuredWidth() - getViewportOffsetX() - mEdgeSwipeRegionSize; + if ((mDownMotionX <= leftEdgeBoundary || mDownMotionX >= rightEdgeBoundary)) { + mDownEventOnEdge = true; + } + if (mTouchState == TOUCH_STATE_SCROLLING) { pageBeginMoving(); } @@ -1259,6 +1435,9 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (mTouchState == TOUCH_STATE_SCROLLING) { // Scroll to follow the motion event final int pointerIndex = ev.findPointerIndex(mActivePointerId); + + if (pointerIndex == -1) return true; + final float x = ev.getX(pointerIndex); final float deltaX = mLastMotionX + mLastMotionXRemainder - x; @@ -1281,6 +1460,112 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } else { awakenScrollBars(); } + } else if (mTouchState == TOUCH_STATE_REORDERING) { + // Update the last motion position + mLastMotionX = ev.getX(); + mLastMotionY = ev.getY(); + + // Update the parent down so that our zoom animations take this new movement into + // account + float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY); + mParentDownMotionX = pt[0]; + mParentDownMotionY = pt[1]; + updateDragViewTranslationDuringDrag(); + + // Find the closest page to the touch point + final int dragViewIndex = indexOfChild(mDragView); + int bufferSize = (int) (REORDERING_SIDE_PAGE_BUFFER_PERCENTAGE * + getViewportWidth()); + int leftBufferEdge = (int) (mapPointFromViewToParent(this, mViewport.left, 0)[0] + + bufferSize); + int rightBufferEdge = (int) (mapPointFromViewToParent(this, mViewport.right, 0)[0] + - bufferSize); + + // Change the drag view if we are hovering over the drop target + boolean isHoveringOverDelete = isHoveringOverDeleteDropTarget( + (int) mParentDownMotionX, (int) mParentDownMotionY); + setPageHoveringOverDeleteDropTarget(dragViewIndex, isHoveringOverDelete); + + if (DEBUG) Log.d(TAG, "leftBufferEdge: " + leftBufferEdge); + if (DEBUG) Log.d(TAG, "rightBufferEdge: " + rightBufferEdge); + if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX); + if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY); + if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX); + if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY); + + float parentX = mParentDownMotionX; + int pageIndexToSnapTo = -1; + if (parentX < leftBufferEdge && dragViewIndex > 0) { + pageIndexToSnapTo = dragViewIndex - 1; + } else if (parentX > rightBufferEdge && dragViewIndex < getChildCount() - 1) { + pageIndexToSnapTo = dragViewIndex + 1; + } + + final int pageUnderPointIndex = pageIndexToSnapTo; + if (pageUnderPointIndex > -1 && !isHoveringOverDelete) { + mTempVisiblePagesRange[0] = 0; + mTempVisiblePagesRange[1] = getPageCount() - 1; + boundByReorderablePages(true, mTempVisiblePagesRange); + if (mTempVisiblePagesRange[0] <= pageUnderPointIndex && + pageUnderPointIndex <= mTempVisiblePagesRange[1] && + pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) { + mSidePageHoverIndex = pageUnderPointIndex; + mSidePageHoverRunnable = new Runnable() { + @Override + public void run() { + // Update the down scroll position to account for the fact that the + // current page is moved + mDownScrollX = getChildOffset(pageUnderPointIndex) + - getRelativeChildOffset(pageUnderPointIndex); + + // Setup the scroll to the correct page before we swap the views + snapToPage(pageUnderPointIndex); + + // For each of the pages between the paged view and the drag view, + // animate them from the previous position to the new position in + // the layout (as a result of the drag view moving in the layout) + int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1; + int lowerIndex = (dragViewIndex < pageUnderPointIndex) ? + dragViewIndex + 1 : pageUnderPointIndex; + int upperIndex = (dragViewIndex > pageUnderPointIndex) ? + dragViewIndex - 1 : pageUnderPointIndex; + for (int i = lowerIndex; i <= upperIndex; ++i) { + View v = getChildAt(i); + // dragViewIndex < pageUnderPointIndex, so after we remove the + // drag view all subsequent views to pageUnderPointIndex will + // shift down. + int oldX = getViewportOffsetX() + getChildOffset(i); + int newX = getViewportOffsetX() + getChildOffset(i + shiftDelta); + + // Animate the view translation from its old position to its new + // position + AnimatorSet anim = (AnimatorSet) v.getTag(ANIM_TAG_KEY); + if (anim != null) { + anim.cancel(); + } + + v.setTranslationX(oldX - newX); + anim = new AnimatorSet(); + anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION); + anim.playTogether( + ObjectAnimator.ofFloat(v, "translationX", 0f)); + anim.start(); + v.setTag(anim); + } + + removeView(mDragView); + onRemoveView(mDragView, false); + addView(mDragView, pageUnderPointIndex); + onAddView(mDragView, pageUnderPointIndex); + mSidePageHoverIndex = -1; + } + }; + postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT); + } + } else { + removeCallbacks(mSidePageHoverRunnable); + mSidePageHoverIndex = -1; + } } else { determineScrollingStart(ev); } @@ -1317,15 +1602,12 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // We give flings precedence over large moves, which is why we short-circuit our // test for a large move if a fling has been registered. That is, a large // move to the left and fling to the right will register as a fling to the right. - final boolean isRtl = isLayoutRtl(); - boolean isDeltaXLeft = isRtl ? deltaX > 0 : deltaX < 0; - boolean isVelocityXLeft = isRtl ? velocityX > 0 : velocityX < 0; - if (((isSignificantMove && !isDeltaXLeft && !isFling) || - (isFling && !isVelocityXLeft)) && mCurrentPage > 0) { + if (((isSignificantMove && deltaX > 0 && !isFling) || + (isFling && velocityX > 0)) && mCurrentPage > 0) { finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1; snapToPageWithVelocity(finalPage, velocityX); - } else if (((isSignificantMove && isDeltaXLeft && !isFling) || - (isFling && isVelocityXLeft)) && + } else if (((isSignificantMove && deltaX < 0 && !isFling) || + (isFling && velocityX < 0)) && mCurrentPage < getChildCount() - 1) { finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1; snapToPageWithVelocity(finalPage, velocityX); @@ -1352,21 +1634,45 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } else { snapToDestination(); } + } else if (mTouchState == TOUCH_STATE_REORDERING) { + // Update the last motion position + mLastMotionX = ev.getX(); + mLastMotionY = ev.getY(); + + // Update the parent down so that our zoom animations take this new movement into + // account + float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY); + mParentDownMotionX = pt[0]; + mParentDownMotionY = pt[1]; + updateDragViewTranslationDuringDrag(); + boolean handledFling = false; + if (!DISABLE_FLING_TO_DELETE) { + // Check the velocity and see if we are flinging-to-delete + PointF flingToDeleteVector = isFlingingToDelete(); + if (flingToDeleteVector != null) { + onFlingToDelete(flingToDeleteVector); + handledFling = true; + } + } + if (!handledFling && isHoveringOverDeleteDropTarget((int) mParentDownMotionX, + (int) mParentDownMotionY)) { + onDropToDelete(); + } } else { onUnhandledTap(ev); } - mTouchState = TOUCH_STATE_REST; - mActivePointerId = INVALID_POINTER; - releaseVelocityTracker(); + + // Remove the callback to wait for the side page hover timeout + removeCallbacks(mSidePageHoverRunnable); + // End any intermediate reordering states + resetTouchState(); break; case MotionEvent.ACTION_CANCEL: if (mTouchState == TOUCH_STATE_SCROLLING) { snapToDestination(); } - mTouchState = TOUCH_STATE_REST; - mActivePointerId = INVALID_POINTER; - releaseVelocityTracker(); + resetTouchState(); break; case MotionEvent.ACTION_POINTER_UP: @@ -1374,9 +1680,25 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc break; } + System.out.println("onTouch, return true"); return true; } + public void onFlingToDelete(View v) {} + public void onRemoveView(View v, boolean deletePermanently) {} + public void onRemoveViewAnimationCompleted() {} + public void onAddView(View v, int index) {} + + private void resetTouchState() { + releaseVelocityTracker(); + endReordering(); + mTouchState = TOUCH_STATE_REST; + mActivePointerId = INVALID_POINTER; + mDownEventOnEdge = false; + } + + protected void onUnhandledTap(MotionEvent ev) {} + @Override public boolean onGenericMotionEvent(MotionEvent event) { if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { @@ -1393,9 +1715,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); } if (hscroll != 0 || vscroll != 0) { - boolean isForwardScroll = isLayoutRtl() ? (hscroll < 0 || vscroll < 0) - : (hscroll > 0 || vscroll > 0); - if (isForwardScroll) { + if (hscroll > 0 || vscroll > 0) { scrollRight(); } else { scrollLeft(); @@ -1441,8 +1761,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } } - protected void onUnhandledTap(MotionEvent ev) {} - @Override public void requestChildFocus(View child, View focused) { super.requestChildFocus(child, focused); @@ -1453,14 +1771,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } protected int getChildIndexForRelativeOffset(int relativeOffset) { - final boolean isRtl = isLayoutRtl(); final int childCount = getChildCount(); int left; int right; - final int startIndex = isRtl ? childCount - 1 : 0; - final int endIndex = isRtl ? -1 : childCount; - final int delta = isRtl ? -1 : 1; - for (int i = startIndex; i != endIndex; i += delta) { + for (int i = 0; i < childCount; ++i) { left = getRelativeChildOffset(i); right = (left + getScaledMeasuredWidth(getPageAt(i))); if (left <= relativeOffset && relativeOffset <= right) { @@ -1478,16 +1792,28 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc return (minWidth > measuredWidth) ? minWidth : measuredWidth; } + int getPageNearestToPoint(float x) { + int index = 0; + for (int i = 0; i < getChildCount(); ++i) { + if (x < getChildAt(i).getRight() - getScrollX()) { + return index; + } else { + index++; + } + } + return Math.min(index, getChildCount() - 1); + } + int getPageNearestToCenterOfScreen() { int minDistanceFromScreenCenter = Integer.MAX_VALUE; int minDistanceFromScreenCenterIndex = -1; - int screenCenter = getScrollX() + (getMeasuredWidth() / 2); + int screenCenter = getViewportOffsetX() + getScrollX() + (getViewportWidth() / 2); final int childCount = getChildCount(); for (int i = 0; i < childCount; ++i) { View layout = (View) getPageAt(i); int childWidth = getScaledMeasuredWidth(layout); int halfChildWidth = (childWidth / 2); - int childCenter = getChildOffset(i) + halfChildWidth; + int childCenter = getViewportOffsetX() + getChildOffset(i) + halfChildWidth; int distanceFromScreenCenter = Math.abs(childCenter - screenCenter); if (distanceFromScreenCenter < minDistanceFromScreenCenter) { minDistanceFromScreenCenter = distanceFromScreenCenter; @@ -1523,11 +1849,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected void snapToPageWithVelocity(int whichPage, int velocity) { whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1)); - int halfScreenSize = getMeasuredWidth() / 2; + int halfScreenSize = getViewportWidth() / 2; if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage)); if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): " - + getMeasuredWidth() + ", " + getChildWidth(whichPage)); + + getViewportWidth() + ", " + getChildWidth(whichPage)); final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); int delta = newX - mUnboundedScrollX; int duration = 0; @@ -1554,7 +1880,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // user flings, so we scale the duration by a value near to the derivative of the scroll // interpolator at zero, ie. 5. We use 4 to make it a little slower. duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); - duration = Math.min(duration, MAX_PAGE_SNAP_DURATION); snapToPage(whichPage, delta, duration); } @@ -1563,21 +1888,32 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); } + protected void snapToPageImmediately(int whichPage) { + snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true); + } + protected void snapToPage(int whichPage, int duration) { + snapToPage(whichPage, duration, false); + } + + protected void snapToPage(int whichPage, int duration, boolean immediate) { whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1)); if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage)); - if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getMeasuredWidth() + ", " + if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getViewportWidth() + ", " + getChildWidth(whichPage)); int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); final int sX = mUnboundedScrollX; final int delta = newX - sX; - snapToPage(whichPage, delta, duration); + snapToPage(whichPage, delta, duration, immediate); } protected void snapToPage(int whichPage, int delta, int duration) { - mNextPage = whichPage; + snapToPage(whichPage, delta, duration, false); + } + protected void snapToPage(int whichPage, int delta, int duration, boolean immediate) { + mNextPage = whichPage; View focusedChild = getFocusedChild(); if (focusedChild != null && whichPage != mCurrentPage && focusedChild == getPageAt(mCurrentPage)) { @@ -1586,21 +1922,23 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc pageBeginMoving(); awakenScrollBars(duration); - if (duration == 0) { + if (immediate) { + duration = 0; + } else if (duration == 0) { duration = Math.abs(delta); } if (!mScroller.isFinished()) mScroller.abortAnimation(); mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration); - // Load associated pages immediately if someone else is handling the scroll, otherwise defer - // loading associated pages until the scroll settles - if (mDeferScrollUpdate) { - loadAssociatedPages(mNextPage); - } else { - mDeferLoadAssociatedPagesUntilScrollCompletes = true; - } notifyPageSwitchListener(); + + // Trigger a compute() to finish switching pages if necessary + if (immediate) { + computeScroll(); + } + + mForceScreenScrolled = true; invalidate(); } @@ -1806,6 +2144,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc hideScrollingIndicator(false); } }; + protected void flashScrollingIndicator(boolean animated) { removeCallbacks(hideScrollingIndicatorRunnable); showScrollingIndicator(!animated); @@ -1825,10 +2164,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc updateScrollingIndicatorPosition(); mScrollIndicator.setVisibility(View.VISIBLE); cancelScrollingIndicatorAnimations(); - if (immediately || mScrollingPaused) { + if (immediately) { mScrollIndicator.setAlpha(1f); } else { - mScrollIndicatorAnimator = LauncherAnimUtils.ofFloat(mScrollIndicator, "alpha", 1f); + mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 1f); mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeInDuration); mScrollIndicatorAnimator.start(); } @@ -1850,11 +2189,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // Fade the indicator out updateScrollingIndicatorPosition(); cancelScrollingIndicatorAnimations(); - if (immediately || mScrollingPaused) { + if (immediately) { mScrollIndicator.setVisibility(View.INVISIBLE); mScrollIndicator.setAlpha(0f); } else { - mScrollIndicatorAnimator = LauncherAnimUtils.ofFloat(mScrollIndicator, "alpha", 0f); + mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 0f); mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeOutDuration); mScrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() { private boolean cancelled = false; @@ -1896,20 +2235,17 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } private void updateScrollingIndicatorPosition() { - final boolean isRtl = isLayoutRtl(); if (!isScrollingIndicatorEnabled()) return; if (mScrollIndicator == null) return; int numPages = getChildCount(); - int pageWidth = getMeasuredWidth(); + int pageWidth = getViewportWidth(); + int lastChildIndex = Math.max(0, getChildCount() - 1); + int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex); int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight; int indicatorWidth = mScrollIndicator.getMeasuredWidth() - mScrollIndicator.getPaddingLeft() - mScrollIndicator.getPaddingRight(); - float scrollPos = isRtl ? mMaxScrollX - getScrollX() : getScrollX(); - float offset = Math.max(0f, Math.min(1f, (float) scrollPos / mMaxScrollX)); - if (isRtl) { - offset = 1f - offset; - } + float offset = Math.max(0f, Math.min(1f, (float) getScrollX() / maxScrollX)); int indicatorSpace = trackWidth / numPages; int indicatorPos = (int) (offset * (trackWidth - indicatorSpace)) + mScrollIndicatorPaddingLeft; if (hasElasticScrollIndicator()) { @@ -1924,10 +2260,469 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mScrollIndicator.setTranslationX(indicatorPos); } - public void showScrollIndicatorTrack() { + // Animate the drag view back to the original position + void animateDragViewToOriginalPosition() { + if (mDragView != null) { + AnimatorSet anim = new AnimatorSet(); + anim.setDuration(REORDERING_DROP_REPOSITION_DURATION); + anim.playTogether( + ObjectAnimator.ofFloat(mDragView, "translationX", 0f), + ObjectAnimator.ofFloat(mDragView, "translationY", 0f)); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + onPostReorderingAnimationCompleted(); + } + }); + anim.start(); + } + } + + // "Zooms out" the PagedView to reveal more side pages + protected boolean zoomOut() { + if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) { + mZoomInOutAnim.cancel(); + } + + if (!(getScaleX() < 1f || getScaleY() < 1f)) { + mZoomInOutAnim = new AnimatorSet(); + mZoomInOutAnim.setDuration(REORDERING_ZOOM_IN_OUT_DURATION); + mZoomInOutAnim.playTogether( + ObjectAnimator.ofFloat(this, "scaleX", mMinScale), + ObjectAnimator.ofFloat(this, "scaleY", mMinScale)); + mZoomInOutAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + // Show the delete drop target + if (mDeleteDropTarget != null) { + mDeleteDropTarget.setVisibility(View.VISIBLE); + mDeleteDropTarget.animate().alpha(1f) + .setDuration(REORDERING_DELETE_DROP_TARGET_FADE_DURATION) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mDeleteDropTarget.setAlpha(0f); + } + }); + } + } + }); + mZoomInOutAnim.start(); + return true; + } + return false; + } + + protected void onStartReordering() { + // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.) + mTouchState = TOUCH_STATE_REORDERING; + mIsReordering = true; + + // Mark all the non-widget pages as invisible + getVisiblePages(mTempVisiblePagesRange); + boundByReorderablePages(true, mTempVisiblePagesRange); + for (int i = 0; i < getPageCount(); ++i) { + if (i < mTempVisiblePagesRange[0] || i > mTempVisiblePagesRange[1]) { + getPageAt(i).setAlpha(0f); + } + } + + // We must invalidate to trigger a redraw to update the layers such that the drag view + // is always drawn on top + invalidate(); + } + + private void onPostReorderingAnimationCompleted() { + // Trigger the callback when reordering has settled + --mPostReorderingPreZoomInRemainingAnimationCount; + if (mPostReorderingPreZoomInRunnable != null && + mPostReorderingPreZoomInRemainingAnimationCount == 0) { + mPostReorderingPreZoomInRunnable.run(); + mPostReorderingPreZoomInRunnable = null; + } + } + + protected void onEndReordering() { + mIsReordering = false; + + // Mark all the non-widget pages as visible again + getVisiblePages(mTempVisiblePagesRange); + boundByReorderablePages(true, mTempVisiblePagesRange); + for (int i = 0; i < getPageCount(); ++i) { + if (i < mTempVisiblePagesRange[0] || i > mTempVisiblePagesRange[1]) { + getPageAt(i).setAlpha(1f); + } + } + } + + public boolean startReordering() { + int dragViewIndex = getPageNearestToCenterOfScreen(); + mTempVisiblePagesRange[0] = 0; + mTempVisiblePagesRange[1] = getPageCount() - 1; + boundByReorderablePages(true, mTempVisiblePagesRange); + mReorderingStarted = true; + + // Check if we are within the reordering range + if (mTempVisiblePagesRange[0] <= dragViewIndex && + dragViewIndex <= mTempVisiblePagesRange[1]) { + if (zoomOut()) { + // Find the drag view under the pointer + mDragView = getChildAt(dragViewIndex); + + onStartReordering(); + } + return true; + } + return false; + } + + boolean isReordering(boolean testTouchState) { + boolean state = mIsReordering; + if (testTouchState) { + state &= (mTouchState == TOUCH_STATE_REORDERING); + } + return state; + } + void endReordering() { + // For simplicity, we call endReordering sometimes even if reordering was never started. + // In that case, we don't want to do anything. + if (!mReorderingStarted) return; + mReorderingStarted = false; + + // If we haven't flung-to-delete the current child, then we just animate the drag view + // back into position + final Runnable onCompleteRunnable = new Runnable() { + @Override + public void run() { + onEndReordering(); + } + }; + if (!mDeferringForDelete) { + mPostReorderingPreZoomInRunnable = new Runnable() { + public void run() { + zoomIn(onCompleteRunnable); + }; + }; + + mPostReorderingPreZoomInRemainingAnimationCount = + NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT; + // Snap to the current page + snapToPage(indexOfChild(mDragView), 0); + // Animate the drag view back to the front position + animateDragViewToOriginalPosition(); + } else { + // Handled in post-delete-animation-callbacks + } + } + + // "Zooms in" the PagedView to highlight the current page + protected boolean zoomIn(final Runnable onCompleteRunnable) { + if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) { + mZoomInOutAnim.cancel(); + } + if (getScaleX() < 1f || getScaleY() < 1f) { + mZoomInOutAnim = new AnimatorSet(); + mZoomInOutAnim.setDuration(REORDERING_ZOOM_IN_OUT_DURATION); + mZoomInOutAnim.playTogether( + ObjectAnimator.ofFloat(this, "scaleX", 1f), + ObjectAnimator.ofFloat(this, "scaleY", 1f)); + mZoomInOutAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + // Hide the delete drop target + if (mDeleteDropTarget != null) { + mDeleteDropTarget.animate().alpha(0f) + .setDuration(REORDERING_DELETE_DROP_TARGET_FADE_DURATION) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mDeleteDropTarget.setVisibility(View.GONE); + } + }); + } + } + @Override + public void onAnimationCancel(Animator animation) { + mDragView = null; + } + @Override + public void onAnimationEnd(Animator animation) { + mDragView = null; + if (onCompleteRunnable != null) { + onCompleteRunnable.run(); + } + } + }); + mZoomInOutAnim.start(); + return true; + } else { + if (onCompleteRunnable != null) { + onCompleteRunnable.run(); + } + } + return false; + } + + /* + * Flinging to delete - IN PROGRESS + */ + private PointF isFlingingToDelete() { + ViewConfiguration config = ViewConfiguration.get(getContext()); + 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(FLING_TO_DELETE_MAX_FLING_DEGREES)) { + return vel; + } + } + return null; } - public void hideScrollIndicatorTrack() { + /** + * 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 View mDragView; + private PointF mVelocity; + private Rect mFrom; + private long mPrevTime; + private float mFriction; + + private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f); + + public FlingAlongVectorAnimatorUpdateListener(View dragView, PointF vel, Rect from, + long startTime, float friction) { + mDragView = dragView; + mVelocity = vel; + mFrom = from; + mPrevTime = startTime; + mFriction = 1f - (mDragView.getResources().getDisplayMetrics().density * friction); + } + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float t = ((Float) animation.getAnimatedValue()).floatValue(); + long curTime = AnimationUtils.currentAnimationTimeMillis(); + + mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f); + mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f); + + mDragView.setTranslationX(mFrom.left); + mDragView.setTranslationY(mFrom.top); + mDragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t)); + + mVelocity.x *= mFriction; + mVelocity.y *= mFriction; + mPrevTime = curTime; + } + }; + + private static final int ANIM_TAG_KEY = 100; + + private Runnable createPostDeleteAnimationRunnable(final View dragView) { + return new Runnable() { + @Override + public void run() { + int dragViewIndex = indexOfChild(dragView); + + // For each of the pages around the drag view, animate them from the previous + // position to the new position in the layout (as a result of the drag view moving + // in the layout) + // NOTE: We can make an assumption here because we have side-bound pages that we + // will always have pages to animate in from the left + getVisiblePages(mTempVisiblePagesRange); + boundByReorderablePages(true, mTempVisiblePagesRange); + boolean isLastWidgetPage = (mTempVisiblePagesRange[0] == mTempVisiblePagesRange[1]); + boolean slideFromLeft = (isLastWidgetPage || + dragViewIndex > mTempVisiblePagesRange[0]); + + // Setup the scroll to the correct page before we swap the views + if (slideFromLeft) { + snapToPageImmediately(dragViewIndex - 1); + } + + int firstIndex = (isLastWidgetPage ? 0 : mTempVisiblePagesRange[0]); + int lastIndex = Math.min(mTempVisiblePagesRange[1], getPageCount() - 1); + int lowerIndex = (slideFromLeft ? firstIndex : dragViewIndex + 1 ); + int upperIndex = (slideFromLeft ? dragViewIndex - 1 : lastIndex); + ArrayList animations = new ArrayList(); + for (int i = lowerIndex; i <= upperIndex; ++i) { + View v = getChildAt(i); + // dragViewIndex < pageUnderPointIndex, so after we remove the + // drag view all subsequent views to pageUnderPointIndex will + // shift down. + int oldX = 0; + int newX = 0; + if (slideFromLeft) { + if (i == 0) { + // Simulate the page being offscreen with the page spacing + oldX = getViewportOffsetX() + getChildOffset(i) - getChildWidth(i) + - mPageSpacing; + } else { + oldX = getViewportOffsetX() + getChildOffset(i - 1); + } + newX = getViewportOffsetX() + getChildOffset(i); + } else { + oldX = getChildOffset(i) - getChildOffset(i - 1); + newX = 0; + } + + // Animate the view translation from its old position to its new + // position + AnimatorSet anim = (AnimatorSet) v.getTag(); + if (anim != null) { + anim.cancel(); + } + + // Note: Hacky, but we want to skip any optimizations to not draw completely + // hidden views + v.setAlpha(Math.max(v.getAlpha(), 0.01f)); + v.setTranslationX(oldX - newX); + anim = new AnimatorSet(); + anim.playTogether( + ObjectAnimator.ofFloat(v, "translationX", 0f), + ObjectAnimator.ofFloat(v, "alpha", 1f)); + animations.add(anim); + v.setTag(ANIM_TAG_KEY, anim); + } + + AnimatorSet slideAnimations = new AnimatorSet(); + slideAnimations.playTogether(animations); + slideAnimations.setDuration(DELETE_SLIDE_IN_SIDE_PAGE_DURATION); + slideAnimations.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + final Runnable onCompleteRunnable = new Runnable() { + @Override + public void run() { + mDeferringForDelete = false; + onEndReordering(); + onRemoveViewAnimationCompleted(); + } + }; + zoomIn(onCompleteRunnable); + } + }); + slideAnimations.start(); + + removeView(dragView); + onRemoveView(dragView, true); + } + }; + } + + public void onFlingToDelete(PointF vel) { + 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 long mStartTime; + private float mOffset; + /* Anonymous inner class ctor */ { + mStartTime = startTime; + } + + @Override + public float getInterpolation(float t) { + if (mCount < 0) { + mCount++; + } else if (mCount == 0) { + mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() - + mStartTime) / FLING_TO_DELETE_FADE_OUT_DURATION); + mCount++; + } + return Math.min(1f, mOffset + t); + } + }; + + final Rect from = new Rect(); + final View dragView = mDragView; + from.left = (int) dragView.getTranslationX(); + from.top = (int) dragView.getTranslationY(); + AnimatorUpdateListener updateCb = new FlingAlongVectorAnimatorUpdateListener(dragView, vel, + from, startTime, FLING_TO_DELETE_FRICTION); + + final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView); + + // Create and start the animation + ValueAnimator mDropAnim = new ValueAnimator(); + mDropAnim.setInterpolator(tInterpolator); + mDropAnim.setDuration(FLING_TO_DELETE_FADE_OUT_DURATION); + mDropAnim.setFloatValues(0f, 1f); + mDropAnim.addUpdateListener(updateCb); + mDropAnim.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + onAnimationEndRunnable.run(); + } + }); + mDropAnim.start(); + mDeferringForDelete = true; + } + + /* Drag to delete */ + private boolean isHoveringOverDeleteDropTarget(int x, int y) { + if (mDeleteDropTarget != null) { + mAltTmpRect.set(0, 0, 0, 0); + View parent = (View) mDeleteDropTarget.getParent(); + if (parent != null) { + parent.getGlobalVisibleRect(mAltTmpRect); + } + mDeleteDropTarget.getGlobalVisibleRect(mTmpRect); + mTmpRect.offset(-mAltTmpRect.left, -mAltTmpRect.top); + return mTmpRect.contains(x, y); + } + return false; + } + + protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) {} + + private void onDropToDelete() { + final View dragView = mDragView; + + final float toScale = 0f; + final float toAlpha = 0f; + + // Create and start the complex animation + ArrayList animations = new ArrayList(); + AnimatorSet motionAnim = new AnimatorSet(); + motionAnim.setInterpolator(new DecelerateInterpolator(2)); + motionAnim.playTogether( + ObjectAnimator.ofFloat(dragView, "scaleX", toScale), + ObjectAnimator.ofFloat(dragView, "scaleY", toScale)); + animations.add(motionAnim); + + AnimatorSet alphaAnim = new AnimatorSet(); + alphaAnim.setInterpolator(new LinearInterpolator()); + alphaAnim.playTogether( + ObjectAnimator.ofFloat(dragView, "alpha", toAlpha)); + animations.add(alphaAnim); + + final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView); + + AnimatorSet anim = new AnimatorSet(); + anim.playTogether(animations); + anim.setDuration(DRAG_TO_DELETE_FADE_OUT_DURATION); + anim.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + onAnimationEndRunnable.run(); + } + }); + anim.start(); + + mDeferringForDelete = true; } /* Accessibility */ @@ -1976,11 +2771,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc return false; } - protected String getCurrentPageDescription() { - return String.format(getContext().getString(R.string.default_scroll_format), - getNextPage() + 1, getChildCount()); - } - @Override public boolean onHoverEvent(android.view.MotionEvent event) { return true; diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 16c52ded5..d366a16d1 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -33,7 +33,9 @@ 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.Matrix; +import android.graphics.Paint; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -96,7 +98,6 @@ public class Workspace extends SmoothPagedView private float mBackgroundAlpha = 0; private float mWallpaperScrollRatio = 1.0f; - private int mOriginalPageSpacing; private final WallpaperManager mWallpaperManager; private IBinder mWindowToken; @@ -147,11 +148,10 @@ public class Workspace extends SmoothPagedView // These are temporary variables to prevent having to allocate a new object just to // return an (x, y) value from helper functions. Do NOT use them to maintain other state. private int[] mTempCell = new int[2]; + private int[] mTempPt = new int[2]; private int[] mTempEstimate = new int[2]; private float[] mDragViewVisualCenter = new float[2]; - private float[] mTempDragCoordinates = new float[2]; private float[] mTempCellLayoutCenterCoordinates = new float[2]; - private float[] mTempDragBottomRightCoordinates = new float[2]; private Matrix mTempInverseMatrix = new Matrix(); private SpringLoadedDragController mSpringLoadedDragController; @@ -194,7 +194,6 @@ public class Workspace extends SmoothPagedView private Point mDisplaySize = new Point(); private boolean mIsStaticWallpaper; private int mWallpaperTravelWidth; - private int mSpringLoadedPageSpacing; private int mCameraDistance; // Variables relating to the creation of user folders by hovering shortcuts over shortcuts @@ -239,24 +238,13 @@ public class Workspace extends SmoothPagedView private int mSavedScrollX; private float mSavedRotationY; private float mSavedTranslationX; - private float mCurrentScaleX; - private float mCurrentScaleY; - private float mCurrentRotationY; - private float mCurrentTranslationX; - private float mCurrentTranslationY; - private float[] mOldTranslationXs; - private float[] mOldTranslationYs; - private float[] mOldScaleXs; - private float[] mOldScaleYs; + + private float mCurrentScale; + private float mNewScale; private float[] mOldBackgroundAlphas; private float[] mOldAlphas; - private float[] mNewTranslationXs; - private float[] mNewTranslationYs; - private float[] mNewScaleXs; - private float[] mNewScaleYs; private float[] mNewBackgroundAlphas; private float[] mNewAlphas; - private float[] mNewRotationYs; private int mLastChildCount = -1; private float mTransitionProgress; @@ -287,7 +275,6 @@ public class Workspace extends SmoothPagedView public Workspace(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContentIsRefreshable = false; - mOriginalPageSpacing = mPageSpacing; mOutlineHelper = HolographicOutlineHelper.obtain(context); @@ -334,8 +321,6 @@ public class Workspace extends SmoothPagedView mSpringLoadedShrinkFactor = res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f; - mSpringLoadedPageSpacing = - res.getDimensionPixelSize(R.dimen.workspace_spring_loaded_page_spacing); mCameraDistance = res.getInteger(R.integer.config_cameraDistance); // if the value is manually specified, use that instead @@ -422,6 +407,7 @@ public class Workspace extends SmoothPagedView setClipChildren(false); setClipToPadding(false); setChildrenDrawnWithCacheEnabled(true); + setMinScale(0.5f); final Resources res = getResources(); try { @@ -499,6 +485,8 @@ public class Workspace extends SmoothPagedView CellLayout newScreen = (CellLayout) mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null); + + newScreen.setOnLongClickListener(mLongClickListener); mWorkspaceScreens.put(screenId, newScreen); mScreenOrder.add(screenId); addView(newScreen, getChildCount()); @@ -710,42 +698,6 @@ public class Workspace extends SmoothPagedView } } - /** - * Check if the point (x, y) hits a given page. - */ - private boolean hitsPage(int index, float x, float y) { - final View page = getChildAt(index); - if (page != null) { - float[] localXY = { x, y }; - mapPointFromSelfToChild(page, localXY); - return (localXY[0] >= 0 && localXY[0] < page.getWidth() - && localXY[1] >= 0 && localXY[1] < page.getHeight()); - } - return false; - } - - @Override - protected boolean hitsPreviousPage(float x, float y) { - // mNextPage is set to INVALID_PAGE whenever we are stationary. - // Calculating "next page" this way ensures that you scroll to whatever page you tap on - final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage; - - // Only allow tap to next page on large devices, where there's significant margin outside - // the active workspace - return LauncherAppState.getInstance().isScreenLarge() && hitsPage(current - 1, x, y); - } - - @Override - protected boolean hitsNextPage(float x, float y) { - // mNextPage is set to INVALID_PAGE whenever we are stationary. - // Calculating "next page" this way ensures that you scroll to whatever page you tap on - final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage; - - // Only allow tap to next page on large devices, where there's significant margin outside - // the active workspace - return LauncherAppState.getInstance().isScreenLarge() && hitsPage(current + 1, x, y); - } - /** * Called directly from a CellLayout (not by the framework), after we've been added as a * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout @@ -1685,19 +1637,11 @@ public class Workspace extends SmoothPagedView private void initAnimationArrays() { final int childCount = getChildCount(); if (mLastChildCount == childCount) return; - mOldTranslationXs = new float[childCount]; - mOldTranslationYs = new float[childCount]; - mOldScaleXs = new float[childCount]; - mOldScaleYs = new float[childCount]; + mOldBackgroundAlphas = new float[childCount]; mOldAlphas = new float[childCount]; - mNewTranslationXs = new float[childCount]; - mNewTranslationYs = new float[childCount]; - mNewScaleXs = new float[childCount]; - mNewScaleYs = new float[childCount]; mNewBackgroundAlphas = new float[childCount]; mNewAlphas = new float[childCount]; - mNewRotationYs = new float[childCount]; } Animator getChangeStateAnimation(final State state, boolean animated) { @@ -1725,28 +1669,19 @@ public class Workspace extends SmoothPagedView final boolean stateIsNormal = (state == State.NORMAL); final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED); final boolean stateIsSmall = (state == State.SMALL); - float finalScaleFactor = 1.0f; float finalBackgroundAlpha = stateIsSpringLoaded ? 1.0f : 0f; - float translationX = 0; - float translationY = 0; boolean zoomIn = true; + mNewScale = 1.0f; if (state != State.NORMAL) { - finalScaleFactor = mSpringLoadedShrinkFactor - (stateIsSmall ? 0.1f : 0); - setPageSpacing(mSpringLoadedPageSpacing); + mNewScale = mSpringLoadedShrinkFactor - (stateIsSmall ? 0.1f : 0); if (oldStateIsNormal && stateIsSmall) { zoomIn = false; - setLayoutScale(finalScaleFactor); updateChildrenLayersEnabled(false); } else { finalBackgroundAlpha = 1.0f; - setLayoutScale(finalScaleFactor); } - } else { - setPageSpacing(mOriginalPageSpacing); - setLayoutScale(1.0f); } - final int duration = zoomIn ? getResources().getInteger(R.integer.config_workspaceUnshrinkTime) : getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime); @@ -1774,49 +1709,30 @@ public class Workspace extends SmoothPagedView mOldAlphas[i] = initialAlpha; mNewAlphas[i] = finalAlpha; if (animated) { - mOldTranslationXs[i] = cl.getTranslationX(); - mOldTranslationYs[i] = cl.getTranslationY(); - mOldScaleXs[i] = cl.getScaleX(); - mOldScaleYs[i] = cl.getScaleY(); mOldBackgroundAlphas[i] = cl.getBackgroundAlpha(); - - mNewTranslationXs[i] = translationX; - mNewTranslationYs[i] = translationY; - mNewScaleXs[i] = finalScaleFactor; - mNewScaleYs[i] = finalScaleFactor; mNewBackgroundAlphas[i] = finalBackgroundAlpha; } else { - cl.setTranslationX(translationX); - cl.setTranslationY(translationY); - cl.setScaleX(finalScaleFactor); - cl.setScaleY(finalScaleFactor); + setScaleX(mNewScale); + setScaleY(mNewScale); cl.setBackgroundAlpha(finalBackgroundAlpha); cl.setShortcutAndWidgetAlpha(finalAlpha); } } if (animated) { + LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this); + scale.scaleX(mNewScale) + .scaleY(mNewScale) + .setInterpolator(mZoomInInterpolator); + anim.play(scale); for (int index = 0; index < getChildCount(); index++) { final int i = index; final CellLayout cl = (CellLayout) getChildAt(i); float currentAlpha = cl.getShortcutsAndWidgets().getAlpha(); 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.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] || currentAlpha != mNewAlphas[i]) { LauncherViewPropertyAnimator alphaAnim = @@ -2694,20 +2610,6 @@ public class Workspace extends SmoothPagedView mLastReorderY = -1; } - public DropTarget getDropTargetDelegate(DragObject d) { - return null; - } - - /* - * - * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's - * coordinate space. The argument xy is modified with the return result. - * - */ - void mapPointFromSelfToChild(View v, float[] xy) { - mapPointFromSelfToChild(v, xy, null); - } - /* * * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's @@ -2719,25 +2621,34 @@ public class Workspace extends SmoothPagedView * */ void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) { - if (cachedInverseMatrix == null) { - v.getMatrix().invert(mTempInverseMatrix); - cachedInverseMatrix = mTempInverseMatrix; + xy[0] = xy[0] - v.getLeft(); + xy[1] = xy[1] - v.getTop(); + } + + boolean isPointInSelfOverHotseat(int x, int y, Rect r) { + if (r == null) { + r = new Rect(); } - int scrollX = getScrollX(); - if (mNextPage != INVALID_PAGE) { - scrollX = mScroller.getFinalX(); + mTempPt[0] = x; + mTempPt[1] = y; + mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true); + mLauncher.getHotseat().getHitRect(r); + if (r.contains(mTempPt[0], mTempPt[1])) { + return true; } - xy[0] = xy[0] + scrollX - v.getLeft(); - xy[1] = xy[1] + getScrollY() - v.getTop(); - cachedInverseMatrix.mapPoints(xy); + return false; } - void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) { - hotseat.getLayout().getMatrix().invert(mTempInverseMatrix); - xy[0] = xy[0] - hotseat.getLeft() - hotseat.getLayout().getLeft(); - xy[1] = xy[1] - hotseat.getTop() - hotseat.getLayout().getTop(); - mTempInverseMatrix.mapPoints(xy); + mTempPt[0] = (int) xy[0]; + mTempPt[1] = (int) xy[1]; + mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true); + + mTempPt[0] -= hotseat.getLeft(); + mTempPt[1] -= hotseat.getTop(); + + xy[0] = mTempPt[0]; + xy[1] = mTempPt[1]; } /* @@ -2747,13 +2658,8 @@ public class Workspace extends SmoothPagedView * */ void mapPointFromChildToSelf(View v, float[] xy) { - v.getMatrix().mapPoints(xy); - int scrollX = getScrollX(); - if (mNextPage != INVALID_PAGE) { - scrollX = mScroller.getFinalX(); - } - xy[0] -= (scrollX - v.getLeft()); - xy[1] -= (getScrollY() - v.getTop()); + xy[0] += v.getLeft(); + xy[1] += v.getTop(); } static private float squaredDistance(float[] point1, float[] point2) { @@ -2762,45 +2668,6 @@ public class Workspace extends SmoothPagedView return distanceX * distanceX + distanceY * distanceY; } - /* - * - * Returns true if the passed CellLayout cl overlaps with dragView - * - */ - boolean overlaps(CellLayout cl, DragView dragView, - int dragViewX, int dragViewY, Matrix cachedInverseMatrix) { - // Transform the coordinates of the item being dragged to the CellLayout's coordinates - final float[] draggedItemTopLeft = mTempDragCoordinates; - draggedItemTopLeft[0] = dragViewX; - draggedItemTopLeft[1] = dragViewY; - final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates; - draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getDragRegionWidth(); - draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getDragRegionHeight(); - - // Transform the dragged item's top left coordinates - // to the CellLayout's local coordinates - mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix); - float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]); - float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]); - - if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) { - // Transform the dragged item's bottom right coordinates - // to the CellLayout's local coordinates - mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix); - float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]); - float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]); - - if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) { - float overlap = (overlapRegionRight - overlapRegionLeft) * - (overlapRegionBottom - overlapRegionTop); - if (overlap > 0) { - return true; - } - } - } - return false; - } - /* * * This method returns the CellLayout that is currently being dragged to. In order to drag @@ -2912,8 +2779,7 @@ public class Workspace extends SmoothPagedView // Identify whether we have dragged over a side page if (isSmall()) { if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) { - mLauncher.getHotseat().getHitRect(r); - if (r.contains(d.x, d.y)) { + if (isPointInSelfOverHotseat(d.x, d.y, r)) { layout = mLauncher.getHotseat().getLayout(); } } @@ -2921,7 +2787,6 @@ public class Workspace extends SmoothPagedView layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false); } if (layout != mDragTargetLayout) { - setCurrentDropLayout(layout); setCurrentDragOverlappingLayout(layout); @@ -2937,8 +2802,7 @@ public class Workspace extends SmoothPagedView } else { // Test to see if we are over the hotseat otherwise just use the current page if (mLauncher.getHotseat() != null && !isDragWidget(d)) { - mLauncher.getHotseat().getHitRect(r); - if (r.contains(d.x, d.y)) { + if (isPointInSelfOverHotseat(d.x, d.y, r)) { layout = mLauncher.getHotseat().getLayout(); } } @@ -3117,10 +2981,10 @@ public class Workspace extends SmoothPagedView } @Override - public void getHitRect(Rect outRect) { + public void getHitRectRelativeToDragLayer(Rect outRect) { // We want the workspace to have the whole area of the display (it will find the correct // cell layout to drop to in the existing drag/drop logic. - outRect.set(0, 0, mDisplaySize.x, mDisplaySize.y); + mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect); } /** @@ -3355,7 +3219,7 @@ public class Workspace extends SmoothPagedView setFinalTransitionTransform(layout); float cellLayoutScale = - mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc); + mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true); resetTransitionTransform(layout); float dragViewScaleX; @@ -3436,31 +3300,15 @@ public class Workspace extends SmoothPagedView public void setFinalTransitionTransform(CellLayout layout) { if (isSwitchingState()) { - int index = indexOfChild(layout); - mCurrentScaleX = layout.getScaleX(); - mCurrentScaleY = layout.getScaleY(); - mCurrentTranslationX = layout.getTranslationX(); - mCurrentTranslationY = layout.getTranslationY(); - mCurrentRotationY = layout.getRotationY(); - layout.setScaleX(mNewScaleXs[index]); - layout.setScaleY(mNewScaleYs[index]); - layout.setTranslationX(mNewTranslationXs[index]); - layout.setTranslationY(mNewTranslationYs[index]); - layout.setRotationY(mNewRotationYs[index]); + mCurrentScale = getScaleX(); + setScaleX(mNewScale); + setScaleY(mNewScale); } } public void resetTransitionTransform(CellLayout layout) { if (isSwitchingState()) { - mCurrentScaleX = layout.getScaleX(); - mCurrentScaleY = layout.getScaleY(); - mCurrentTranslationX = layout.getTranslationX(); - mCurrentTranslationY = layout.getTranslationY(); - mCurrentRotationY = layout.getRotationY(); - layout.setScaleX(mCurrentScaleX); - layout.setScaleY(mCurrentScaleY); - layout.setTranslationX(mCurrentTranslationX); - layout.setTranslationY(mCurrentTranslationY); - layout.setRotationY(mCurrentRotationY); + setScaleX(mCurrentScale); + setScaleY(mCurrentScale); } } @@ -4114,7 +3962,6 @@ public class Workspace extends SmoothPagedView public void syncPageItems(int page, boolean immediate) { } - @Override protected String getCurrentPageDescription() { int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; return String.format(getContext().getString(R.string.workspace_scroll_format), -- cgit v1.2.3