summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/PagedView.java
diff options
context:
space:
mode:
authorAdam Cohen <adamcohen@google.com>2013-07-01 17:03:59 -0700
committerAdam Cohen <adamcohen@google.com>2013-07-08 18:32:57 -0700
commit7d30a37007bac318db1c9af47a9af12d348042a5 (patch)
tree83524dc8cd4dd05c0e8ce3a9f5607e3e7c08efee /src/com/android/launcher3/PagedView.java
parent70c987a0fea53583b22650bed6251e2dcc68bdac (diff)
downloadandroid_packages_apps_Trebuchet-7d30a37007bac318db1c9af47a9af12d348042a5.tar.gz
android_packages_apps_Trebuchet-7d30a37007bac318db1c9af47a9af12d348042a5.tar.bz2
android_packages_apps_Trebuchet-7d30a37007bac318db1c9af47a9af12d348042a5.zip
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
Diffstat (limited to 'src/com/android/launcher3/PagedView.java')
-rw-r--r--src/com/android/launcher3/PagedView.java1288
1 files changed, 1039 insertions, 249 deletions
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<View> 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<Animator> animations = new ArrayList<Animator>();
+ 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<Animator> animations = new ArrayList<Animator>();
+ 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;