Loading src/com/android/launcher3/PagedView.java +78 −59 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import android.animation.TimeInterpolator; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; import android.os.Bundle; Loading @@ -49,7 +48,6 @@ import android.view.animation.Interpolator; import com.android.launcher3.anim.PropertyListBuilder; import com.android.launcher3.pageindicators.PageIndicator; import com.android.launcher3.util.LauncherEdgeEffect; import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; Loading @@ -70,6 +68,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc public static final int PAGE_SNAP_ANIMATION_DURATION = 750; protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950; // Overscroll constants private final static int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270; private static final float OVERSCROLL_ACCELERATE_FACTOR = 2; private static final float OVERSCROLL_DAMP_FACTOR = 0.07f; private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; // The page is moved more than halfway, automatically move to the next page on touch up. private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f; Loading Loading @@ -145,6 +148,13 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected boolean mWasInOverscroll = false; // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise // it is equal to the scaled overscroll position. We use a separate value so as to prevent // the screens from continuing to translate beyond the normal bounds. protected int mOverScrollX; protected int mUnboundedScrollX; // Page Indicator @Thunk int mPageIndicatorViewId; protected PageIndicator mPageIndicator; Loading Loading @@ -184,10 +194,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected final Rect mInsets = new Rect(); protected final boolean mIsRtl; // Edge effect private final LauncherEdgeEffect mEdgeGlowLeft = new LauncherEdgeEffect(); private final LauncherEdgeEffect mEdgeGlowRight = new LauncherEdgeEffect(); public PagedView(Context context) { this(context, null); } Loading Loading @@ -229,8 +235,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc setWillNotDraw(false); int edgeEffectColor = Themes.getAttrColor(getContext(), android.R.attr.colorEdgeEffect); mEdgeGlowLeft.setColor(edgeEffectColor); mEdgeGlowRight.setColor(edgeEffectColor); } protected void setDefaultInterpolator(Interpolator interpolator) { Loading Loading @@ -476,7 +480,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } protected int getUnboundedScrollX() { return getScrollX(); return mUnboundedScrollX; } @Override Loading @@ -499,6 +503,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc x = Math.max(x, mFreeScrollMinScrollX); } mUnboundedScrollX = x; boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < 0); boolean isXAfterLastPage = mIsRtl ? (x < 0) : (x > mMaxScrollX); if (isXBeforeFirstPage) { Loading Loading @@ -526,6 +532,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc overScroll(0); mWasInOverscroll = false; } mOverScrollX = x; super.scrollTo(x, y); } Loading Loading @@ -565,7 +572,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (mScroller.computeScrollOffset()) { // Don't bother scrolling if the page does not need to be moved if (getUnboundedScrollX() != mScroller.getCurrX() || getScrollY() != mScroller.getCurrY()) { || getScrollY() != mScroller.getCurrY() || mOverScrollX != mScroller.getCurrX()) { float scaleX = mFreeScroll ? getScaleX() : 1f; int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX)); scrollTo(scrollX, mScroller.getCurrY()); Loading Loading @@ -972,47 +980,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc range[1] = Math.max(0, getChildCount() - 1); } @Override public void draw(Canvas canvas) { super.draw(canvas); if (getPageCount() > 0) { if (!mEdgeGlowLeft.isFinished()) { final int restoreCount = canvas.save(); Rect display = mViewport; canvas.translate(display.left, display.top); canvas.rotate(270); getEdgeVerticalPosition(sTmpIntPoint); canvas.translate(display.top - sTmpIntPoint[1], 0); mEdgeGlowLeft.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width()); if (mEdgeGlowLeft.draw(canvas)) { postInvalidateOnAnimation(); } canvas.restoreToCount(restoreCount); } if (!mEdgeGlowRight.isFinished()) { final int restoreCount = canvas.save(); Rect display = mViewport; canvas.translate(display.left + mPageScrolls[mIsRtl ? 0 : (getPageCount() - 1)], display.top); canvas.rotate(90); getEdgeVerticalPosition(sTmpIntPoint); canvas.translate(sTmpIntPoint[0] - display.top, -display.width()); mEdgeGlowRight.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width()); if (mEdgeGlowRight.draw(canvas)) { postInvalidateOnAnimation(); } canvas.restoreToCount(restoreCount); } } } /** * Returns the top and bottom position for the edge effect. */ protected abstract void getEdgeVerticalPosition(int[] pos); @Override public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { int page = indexToPage(indexOfChild(child)); Loading Loading @@ -1335,6 +1302,29 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } } // This curve determines how the effect of scrolling over the limits of the page dimishes // as the user pulls further and further from the bounds private float overScrollInfluenceCurve(float f) { f -= 1.0f; return f * f * f + 1.0f; } protected float acceleratedOverFactor(float amount) { int screenSize = getViewportWidth(); // We want to reach the max over scroll effect when the user has // over scrolled half the size of the screen float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize); if (Float.compare(f, 0f) == 0) return 0; // Clamp this factor, f, to -1 < f < 1 if (Math.abs(f) >= 1) { f /= Math.abs(f); } return f; } // While layout transitions are occurring, a child's position may stray from its baseline // position. This method returns the magnitude of this stray at any given time. public int getLayoutTransitionOffsetForPage(int index) { Loading @@ -1356,13 +1346,25 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected void dampedOverScroll(float amount) { int screenSize = getViewportWidth(); float f = (amount / screenSize); if (f < 0) { mEdgeGlowLeft.onPull(-f); } else if (f > 0) { mEdgeGlowRight.onPull(f); if (Float.compare(f, 0f) == 0) return; f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); // Clamp this factor, f, to -1 < f < 1 if (Math.abs(f) >= 1) { f /= Math.abs(f); } int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize); if (amount < 0) { mOverScrollX = overScrollAmount; super.scrollTo(mOverScrollX, getScrollY()); } else { return; mOverScrollX = mMaxScrollX + overScrollAmount; super.scrollTo(mOverScrollX, getScrollY()); } invalidate(); } Loading @@ -1371,6 +1373,14 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc dampedOverScroll(amount); } protected float maxOverScroll() { // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not // exceed). Used to find out how much extra wallpaper we need for the over scroll effect float f = 1.0f; f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); return OVERSCROLL_DAMP_FACTOR * f; } /** * return true if freescroll has been enabled, false otherwise */ Loading Loading @@ -1715,8 +1725,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mCancelTap = false; mTouchState = TOUCH_STATE_REST; mActivePointerId = INVALID_POINTER; mEdgeGlowLeft.onRelease(); mEdgeGlowRight.onRelease(); } /** Loading Loading @@ -1830,7 +1838,18 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } protected void snapToDestination() { snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION); snapToPage(getPageNearestToCenterOfScreen(), getPageSnapDuration()); } protected boolean isInOverScroll() { return (mOverScrollX > mMaxScrollX || mOverScrollX < 0); } protected int getPageSnapDuration() { if (isInOverScroll()) { return OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION; } return PAGE_SNAP_ANIMATION_DURATION; } public static class ScrollInterpolator implements Interpolator { Loading src/com/android/launcher3/Workspace.java +2 −24 Original line number Diff line number Diff line Loading @@ -300,8 +300,7 @@ public class Workspace extends PagedView boolean mScrollInteractionBegan; boolean mStartedSendingScrollEvents; float mLastOverlayScroll = 0; // Total over scrollX in the overlay direction. private int mUnboundedScrollX; private boolean mForceDrawAdjacentPages = false; // Total over scrollX in the overlay direction. private float mOverlayTranslation; Loading Loading @@ -1321,18 +1320,10 @@ public class Workspace extends PagedView onOverlayScrollChanged(0); } @Override protected int getUnboundedScrollX() { if (isScrollingOverlay()) { return mUnboundedScrollX; } return super.getUnboundedScrollX(); } private boolean isScrollingOverlay() { return mLauncherOverlay != null && ((mIsRtl && mUnboundedScrollX > mMaxScrollX) || (!mIsRtl && mUnboundedScrollX < 0)); ((mIsRtl && getUnboundedScrollX() > mMaxScrollX) || (!mIsRtl && getUnboundedScrollX() < 0)); } @Override Loading @@ -1351,12 +1342,6 @@ public class Workspace extends PagedView } } @Override public void scrollTo(int x, int y) { mUnboundedScrollX = x; super.scrollTo(x, y); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); Loading Loading @@ -1529,13 +1514,6 @@ public class Workspace extends PagedView } } @Override protected void getEdgeVerticalPosition(int[] pos) { View child = getChildAt(getPageCount() - 1); pos[0] = child.getTop(); pos[1] = child.getBottom(); } @Override protected void notifyPageSwitchListener() { super.notifyPageSwitchListener(); Loading src/com/android/launcher3/folder/FolderPagedView.java +0 −6 Original line number Diff line number Diff line Loading @@ -701,10 +701,4 @@ public class FolderPagedView extends PagedView { public int itemsPerPage() { return mMaxItemsPerPage; } @Override protected void getEdgeVerticalPosition(int[] pos) { pos[0] = 0; pos[1] = getViewportHeight(); } } src/com/android/launcher3/util/LauncherEdgeEffect.javadeleted 100644 → 0 +0 −365 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.util; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; /** * This class differs from the framework {@link android.widget.EdgeEffect}: * 1) It does not use PorterDuffXfermode * 2) The width to radius factor is smaller (0.5 instead of 0.75) */ public class LauncherEdgeEffect { // Time it will take the effect to fully recede in ms private static final int RECEDE_TIME = 600; // Time it will take before a pulled glow begins receding in ms private static final int PULL_TIME = 167; // Time it will take in ms for a pulled glow to decay to partial strength before release private static final int PULL_DECAY_TIME = 2000; private static final float MAX_ALPHA = 0.5f; private static final float MAX_GLOW_SCALE = 2.f; private static final float PULL_GLOW_BEGIN = 0.f; // Minimum velocity that will be absorbed private static final int MIN_VELOCITY = 100; // Maximum velocity, clamps at this value private static final int MAX_VELOCITY = 10000; private static final float EPSILON = 0.001f; private static final double ANGLE = Math.PI / 6; private static final float SIN = (float) Math.sin(ANGLE); private static final float COS = (float) Math.cos(ANGLE); private float mGlowAlpha; private float mGlowScaleY; private float mGlowAlphaStart; private float mGlowAlphaFinish; private float mGlowScaleYStart; private float mGlowScaleYFinish; private long mStartTime; private float mDuration; private final Interpolator mInterpolator; private static final int STATE_IDLE = 0; private static final int STATE_PULL = 1; private static final int STATE_ABSORB = 2; private static final int STATE_RECEDE = 3; private static final int STATE_PULL_DECAY = 4; private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 0.8f; private static final int VELOCITY_GLOW_FACTOR = 6; private int mState = STATE_IDLE; private float mPullDistance; private final Rect mBounds = new Rect(); private final Paint mPaint = new Paint(); private float mRadius; private float mBaseGlowScale; private float mDisplacement = 0.5f; private float mTargetDisplacement = 0.5f; /** * Construct a new EdgeEffect with a theme appropriate for the provided context. */ public LauncherEdgeEffect() { mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); mInterpolator = new DecelerateInterpolator(); } /** * Set the size of this edge effect in pixels. * * @param width Effect width in pixels * @param height Effect height in pixels */ public void setSize(int width, int height) { final float r = width * 0.5f / SIN; final float y = COS * r; final float h = r - y; final float or = height * 0.75f / SIN; final float oy = COS * or; final float oh = or - oy; mRadius = r; mBaseGlowScale = h > 0 ? Math.min(oh / h, 1.f) : 1.f; mBounds.set(mBounds.left, mBounds.top, width, (int) Math.min(height, h)); } /** * Reports if this EdgeEffect's animation is finished. If this method returns false * after a call to {@link #draw(Canvas)} the host widget should schedule another * drawing pass to continue the animation. * * @return true if animation is finished, false if drawing should continue on the next frame. */ public boolean isFinished() { return mState == STATE_IDLE; } /** * Immediately finish the current animation. * After this call {@link #isFinished()} will return true. */ public void finish() { mState = STATE_IDLE; } /** * A view should call this when content is pulled away from an edge by the user. * This will update the state of the current visual effect and its associated animation. * The host view should always {@link android.view.View#invalidate()} after this * and draw the results accordingly. * * <p>Views using EdgeEffect should favor {@link #onPull(float, float)} when the displacement * of the pull point is known.</p> * * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to * 1.f (full length of the view) or negative values to express change * back toward the edge reached to initiate the effect. */ public void onPull(float deltaDistance) { onPull(deltaDistance, 0.5f); } /** * A view should call this when content is pulled away from an edge by the user. * This will update the state of the current visual effect and its associated animation. * The host view should always {@link android.view.View#invalidate()} after this * and draw the results accordingly. * * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to * 1.f (full length of the view) or negative values to express change * back toward the edge reached to initiate the effect. * @param displacement The displacement from the starting side of the effect of the point * initiating the pull. In the case of touch this is the finger position. * Values may be from 0-1. */ public void onPull(float deltaDistance, float displacement) { final long now = AnimationUtils.currentAnimationTimeMillis(); mTargetDisplacement = displacement; if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) { return; } if (mState != STATE_PULL) { mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY); } mState = STATE_PULL; mStartTime = now; mDuration = PULL_TIME; mPullDistance += deltaDistance; final float absdd = Math.abs(deltaDistance); mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA, mGlowAlpha + (absdd * PULL_DISTANCE_ALPHA_GLOW_FACTOR)); if (mPullDistance == 0) { mGlowScaleY = mGlowScaleYStart = 0; } else { final float scale = (float) (Math.max(0, 1 - 1 / Math.sqrt(Math.abs(mPullDistance) * mBounds.height()) - 0.3d) / 0.7d); mGlowScaleY = mGlowScaleYStart = scale; } mGlowAlphaFinish = mGlowAlpha; mGlowScaleYFinish = mGlowScaleY; } /** * Call when the object is released after being pulled. * This will begin the "decay" phase of the effect. After calling this method * the host view should {@link android.view.View#invalidate()} and thereby * draw the results accordingly. */ public void onRelease() { mPullDistance = 0; if (mState != STATE_PULL && mState != STATE_PULL_DECAY) { return; } mState = STATE_RECEDE; mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; mGlowAlphaFinish = 0.f; mGlowScaleYFinish = 0.f; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = RECEDE_TIME; } /** * Call when the effect absorbs an impact at the given velocity. * Used when a fling reaches the scroll boundary. * * <p>When using a {@link android.widget.Scroller} or {@link android.widget.OverScroller}, * the method <code>getCurrVelocity</code> will provide a reasonable approximation * to use here.</p> * * @param velocity Velocity at impact in pixels per second. */ public void onAbsorb(int velocity) { mState = STATE_ABSORB; velocity = Math.min(Math.max(MIN_VELOCITY, Math.abs(velocity)), MAX_VELOCITY); mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = 0.15f + (velocity * 0.02f); // The glow depends more on the velocity, and therefore starts out // nearly invisible. mGlowAlphaStart = 0.3f; mGlowScaleYStart = Math.max(mGlowScaleY, 0.f); // Growth for the size of the glow should be quadratic to properly // respond // to a user's scrolling speed. The faster the scrolling speed, the more // intense the effect should be for both the size and the saturation. mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f) / 2, 1.f); // Alpha should change for the glow as well as size. mGlowAlphaFinish = Math.max( mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA)); mTargetDisplacement = 0.5f; } /** * Set the color of this edge effect in argb. * * @param color Color in argb */ public void setColor(int color) { mPaint.setColor(color); } /** * Return the color of this edge effect in argb. * @return The color of this edge effect in argb */ public int getColor() { return mPaint.getColor(); } /** * Draw into the provided canvas. Assumes that the canvas has been rotated * accordingly and the size has been set. The effect will be drawn the full * width of X=0 to X=width, beginning from Y=0 and extending to some factor < * 1.f of height. * * @param canvas Canvas to draw into * @return true if drawing should continue beyond this frame to continue the * animation */ public boolean draw(Canvas canvas) { update(); final float centerX = mBounds.centerX(); final float centerY = mBounds.height() - mRadius; canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0); final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f; float translateX = mBounds.width() * displacement / 2; mPaint.setAlpha((int) (0xff * mGlowAlpha)); canvas.drawCircle(centerX + translateX, centerY, mRadius, mPaint); boolean oneLastFrame = false; if (mState == STATE_RECEDE && mGlowScaleY == 0) { mState = STATE_IDLE; oneLastFrame = true; } return mState != STATE_IDLE || oneLastFrame; } /** * Return the maximum height that the edge effect will be drawn at given the original * {@link #setSize(int, int) input size}. * @return The maximum height of the edge effect */ public int getMaxHeight() { return (int) (mBounds.height() * MAX_GLOW_SCALE + 0.5f); } private void update() { final long time = AnimationUtils.currentAnimationTimeMillis(); final float t = Math.min((time - mStartTime) / mDuration, 1.f); final float interp = mInterpolator.getInterpolation(t); mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp; mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp; mDisplacement = (mDisplacement + mTargetDisplacement) / 2; if (t >= 1.f - EPSILON) { switch (mState) { case STATE_ABSORB: mState = STATE_RECEDE; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = RECEDE_TIME; mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; // After absorb, the glow should fade to nothing. mGlowAlphaFinish = 0.f; mGlowScaleYFinish = 0.f; break; case STATE_PULL: mState = STATE_PULL_DECAY; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = PULL_DECAY_TIME; mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; // After pull, the glow should fade to nothing. mGlowAlphaFinish = 0.f; mGlowScaleYFinish = 0.f; break; case STATE_PULL_DECAY: mState = STATE_RECEDE; break; case STATE_RECEDE: mState = STATE_IDLE; break; } } } } Loading
src/com/android/launcher3/PagedView.java +78 −59 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import android.animation.TimeInterpolator; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; import android.os.Bundle; Loading @@ -49,7 +48,6 @@ import android.view.animation.Interpolator; import com.android.launcher3.anim.PropertyListBuilder; import com.android.launcher3.pageindicators.PageIndicator; import com.android.launcher3.util.LauncherEdgeEffect; import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; Loading @@ -70,6 +68,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc public static final int PAGE_SNAP_ANIMATION_DURATION = 750; protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950; // Overscroll constants private final static int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270; private static final float OVERSCROLL_ACCELERATE_FACTOR = 2; private static final float OVERSCROLL_DAMP_FACTOR = 0.07f; private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; // The page is moved more than halfway, automatically move to the next page on touch up. private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f; Loading Loading @@ -145,6 +148,13 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected boolean mWasInOverscroll = false; // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise // it is equal to the scaled overscroll position. We use a separate value so as to prevent // the screens from continuing to translate beyond the normal bounds. protected int mOverScrollX; protected int mUnboundedScrollX; // Page Indicator @Thunk int mPageIndicatorViewId; protected PageIndicator mPageIndicator; Loading Loading @@ -184,10 +194,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected final Rect mInsets = new Rect(); protected final boolean mIsRtl; // Edge effect private final LauncherEdgeEffect mEdgeGlowLeft = new LauncherEdgeEffect(); private final LauncherEdgeEffect mEdgeGlowRight = new LauncherEdgeEffect(); public PagedView(Context context) { this(context, null); } Loading Loading @@ -229,8 +235,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc setWillNotDraw(false); int edgeEffectColor = Themes.getAttrColor(getContext(), android.R.attr.colorEdgeEffect); mEdgeGlowLeft.setColor(edgeEffectColor); mEdgeGlowRight.setColor(edgeEffectColor); } protected void setDefaultInterpolator(Interpolator interpolator) { Loading Loading @@ -476,7 +480,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } protected int getUnboundedScrollX() { return getScrollX(); return mUnboundedScrollX; } @Override Loading @@ -499,6 +503,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc x = Math.max(x, mFreeScrollMinScrollX); } mUnboundedScrollX = x; boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < 0); boolean isXAfterLastPage = mIsRtl ? (x < 0) : (x > mMaxScrollX); if (isXBeforeFirstPage) { Loading Loading @@ -526,6 +532,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc overScroll(0); mWasInOverscroll = false; } mOverScrollX = x; super.scrollTo(x, y); } Loading Loading @@ -565,7 +572,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (mScroller.computeScrollOffset()) { // Don't bother scrolling if the page does not need to be moved if (getUnboundedScrollX() != mScroller.getCurrX() || getScrollY() != mScroller.getCurrY()) { || getScrollY() != mScroller.getCurrY() || mOverScrollX != mScroller.getCurrX()) { float scaleX = mFreeScroll ? getScaleX() : 1f; int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX)); scrollTo(scrollX, mScroller.getCurrY()); Loading Loading @@ -972,47 +980,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc range[1] = Math.max(0, getChildCount() - 1); } @Override public void draw(Canvas canvas) { super.draw(canvas); if (getPageCount() > 0) { if (!mEdgeGlowLeft.isFinished()) { final int restoreCount = canvas.save(); Rect display = mViewport; canvas.translate(display.left, display.top); canvas.rotate(270); getEdgeVerticalPosition(sTmpIntPoint); canvas.translate(display.top - sTmpIntPoint[1], 0); mEdgeGlowLeft.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width()); if (mEdgeGlowLeft.draw(canvas)) { postInvalidateOnAnimation(); } canvas.restoreToCount(restoreCount); } if (!mEdgeGlowRight.isFinished()) { final int restoreCount = canvas.save(); Rect display = mViewport; canvas.translate(display.left + mPageScrolls[mIsRtl ? 0 : (getPageCount() - 1)], display.top); canvas.rotate(90); getEdgeVerticalPosition(sTmpIntPoint); canvas.translate(sTmpIntPoint[0] - display.top, -display.width()); mEdgeGlowRight.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width()); if (mEdgeGlowRight.draw(canvas)) { postInvalidateOnAnimation(); } canvas.restoreToCount(restoreCount); } } } /** * Returns the top and bottom position for the edge effect. */ protected abstract void getEdgeVerticalPosition(int[] pos); @Override public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { int page = indexToPage(indexOfChild(child)); Loading Loading @@ -1335,6 +1302,29 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } } // This curve determines how the effect of scrolling over the limits of the page dimishes // as the user pulls further and further from the bounds private float overScrollInfluenceCurve(float f) { f -= 1.0f; return f * f * f + 1.0f; } protected float acceleratedOverFactor(float amount) { int screenSize = getViewportWidth(); // We want to reach the max over scroll effect when the user has // over scrolled half the size of the screen float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize); if (Float.compare(f, 0f) == 0) return 0; // Clamp this factor, f, to -1 < f < 1 if (Math.abs(f) >= 1) { f /= Math.abs(f); } return f; } // While layout transitions are occurring, a child's position may stray from its baseline // position. This method returns the magnitude of this stray at any given time. public int getLayoutTransitionOffsetForPage(int index) { Loading @@ -1356,13 +1346,25 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected void dampedOverScroll(float amount) { int screenSize = getViewportWidth(); float f = (amount / screenSize); if (f < 0) { mEdgeGlowLeft.onPull(-f); } else if (f > 0) { mEdgeGlowRight.onPull(f); if (Float.compare(f, 0f) == 0) return; f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); // Clamp this factor, f, to -1 < f < 1 if (Math.abs(f) >= 1) { f /= Math.abs(f); } int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize); if (amount < 0) { mOverScrollX = overScrollAmount; super.scrollTo(mOverScrollX, getScrollY()); } else { return; mOverScrollX = mMaxScrollX + overScrollAmount; super.scrollTo(mOverScrollX, getScrollY()); } invalidate(); } Loading @@ -1371,6 +1373,14 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc dampedOverScroll(amount); } protected float maxOverScroll() { // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not // exceed). Used to find out how much extra wallpaper we need for the over scroll effect float f = 1.0f; f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); return OVERSCROLL_DAMP_FACTOR * f; } /** * return true if freescroll has been enabled, false otherwise */ Loading Loading @@ -1715,8 +1725,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mCancelTap = false; mTouchState = TOUCH_STATE_REST; mActivePointerId = INVALID_POINTER; mEdgeGlowLeft.onRelease(); mEdgeGlowRight.onRelease(); } /** Loading Loading @@ -1830,7 +1838,18 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } protected void snapToDestination() { snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION); snapToPage(getPageNearestToCenterOfScreen(), getPageSnapDuration()); } protected boolean isInOverScroll() { return (mOverScrollX > mMaxScrollX || mOverScrollX < 0); } protected int getPageSnapDuration() { if (isInOverScroll()) { return OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION; } return PAGE_SNAP_ANIMATION_DURATION; } public static class ScrollInterpolator implements Interpolator { Loading
src/com/android/launcher3/Workspace.java +2 −24 Original line number Diff line number Diff line Loading @@ -300,8 +300,7 @@ public class Workspace extends PagedView boolean mScrollInteractionBegan; boolean mStartedSendingScrollEvents; float mLastOverlayScroll = 0; // Total over scrollX in the overlay direction. private int mUnboundedScrollX; private boolean mForceDrawAdjacentPages = false; // Total over scrollX in the overlay direction. private float mOverlayTranslation; Loading Loading @@ -1321,18 +1320,10 @@ public class Workspace extends PagedView onOverlayScrollChanged(0); } @Override protected int getUnboundedScrollX() { if (isScrollingOverlay()) { return mUnboundedScrollX; } return super.getUnboundedScrollX(); } private boolean isScrollingOverlay() { return mLauncherOverlay != null && ((mIsRtl && mUnboundedScrollX > mMaxScrollX) || (!mIsRtl && mUnboundedScrollX < 0)); ((mIsRtl && getUnboundedScrollX() > mMaxScrollX) || (!mIsRtl && getUnboundedScrollX() < 0)); } @Override Loading @@ -1351,12 +1342,6 @@ public class Workspace extends PagedView } } @Override public void scrollTo(int x, int y) { mUnboundedScrollX = x; super.scrollTo(x, y); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); Loading Loading @@ -1529,13 +1514,6 @@ public class Workspace extends PagedView } } @Override protected void getEdgeVerticalPosition(int[] pos) { View child = getChildAt(getPageCount() - 1); pos[0] = child.getTop(); pos[1] = child.getBottom(); } @Override protected void notifyPageSwitchListener() { super.notifyPageSwitchListener(); Loading
src/com/android/launcher3/folder/FolderPagedView.java +0 −6 Original line number Diff line number Diff line Loading @@ -701,10 +701,4 @@ public class FolderPagedView extends PagedView { public int itemsPerPage() { return mMaxItemsPerPage; } @Override protected void getEdgeVerticalPosition(int[] pos) { pos[0] = 0; pos[1] = getViewportHeight(); } }
src/com/android/launcher3/util/LauncherEdgeEffect.javadeleted 100644 → 0 +0 −365 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.util; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; /** * This class differs from the framework {@link android.widget.EdgeEffect}: * 1) It does not use PorterDuffXfermode * 2) The width to radius factor is smaller (0.5 instead of 0.75) */ public class LauncherEdgeEffect { // Time it will take the effect to fully recede in ms private static final int RECEDE_TIME = 600; // Time it will take before a pulled glow begins receding in ms private static final int PULL_TIME = 167; // Time it will take in ms for a pulled glow to decay to partial strength before release private static final int PULL_DECAY_TIME = 2000; private static final float MAX_ALPHA = 0.5f; private static final float MAX_GLOW_SCALE = 2.f; private static final float PULL_GLOW_BEGIN = 0.f; // Minimum velocity that will be absorbed private static final int MIN_VELOCITY = 100; // Maximum velocity, clamps at this value private static final int MAX_VELOCITY = 10000; private static final float EPSILON = 0.001f; private static final double ANGLE = Math.PI / 6; private static final float SIN = (float) Math.sin(ANGLE); private static final float COS = (float) Math.cos(ANGLE); private float mGlowAlpha; private float mGlowScaleY; private float mGlowAlphaStart; private float mGlowAlphaFinish; private float mGlowScaleYStart; private float mGlowScaleYFinish; private long mStartTime; private float mDuration; private final Interpolator mInterpolator; private static final int STATE_IDLE = 0; private static final int STATE_PULL = 1; private static final int STATE_ABSORB = 2; private static final int STATE_RECEDE = 3; private static final int STATE_PULL_DECAY = 4; private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 0.8f; private static final int VELOCITY_GLOW_FACTOR = 6; private int mState = STATE_IDLE; private float mPullDistance; private final Rect mBounds = new Rect(); private final Paint mPaint = new Paint(); private float mRadius; private float mBaseGlowScale; private float mDisplacement = 0.5f; private float mTargetDisplacement = 0.5f; /** * Construct a new EdgeEffect with a theme appropriate for the provided context. */ public LauncherEdgeEffect() { mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); mInterpolator = new DecelerateInterpolator(); } /** * Set the size of this edge effect in pixels. * * @param width Effect width in pixels * @param height Effect height in pixels */ public void setSize(int width, int height) { final float r = width * 0.5f / SIN; final float y = COS * r; final float h = r - y; final float or = height * 0.75f / SIN; final float oy = COS * or; final float oh = or - oy; mRadius = r; mBaseGlowScale = h > 0 ? Math.min(oh / h, 1.f) : 1.f; mBounds.set(mBounds.left, mBounds.top, width, (int) Math.min(height, h)); } /** * Reports if this EdgeEffect's animation is finished. If this method returns false * after a call to {@link #draw(Canvas)} the host widget should schedule another * drawing pass to continue the animation. * * @return true if animation is finished, false if drawing should continue on the next frame. */ public boolean isFinished() { return mState == STATE_IDLE; } /** * Immediately finish the current animation. * After this call {@link #isFinished()} will return true. */ public void finish() { mState = STATE_IDLE; } /** * A view should call this when content is pulled away from an edge by the user. * This will update the state of the current visual effect and its associated animation. * The host view should always {@link android.view.View#invalidate()} after this * and draw the results accordingly. * * <p>Views using EdgeEffect should favor {@link #onPull(float, float)} when the displacement * of the pull point is known.</p> * * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to * 1.f (full length of the view) or negative values to express change * back toward the edge reached to initiate the effect. */ public void onPull(float deltaDistance) { onPull(deltaDistance, 0.5f); } /** * A view should call this when content is pulled away from an edge by the user. * This will update the state of the current visual effect and its associated animation. * The host view should always {@link android.view.View#invalidate()} after this * and draw the results accordingly. * * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to * 1.f (full length of the view) or negative values to express change * back toward the edge reached to initiate the effect. * @param displacement The displacement from the starting side of the effect of the point * initiating the pull. In the case of touch this is the finger position. * Values may be from 0-1. */ public void onPull(float deltaDistance, float displacement) { final long now = AnimationUtils.currentAnimationTimeMillis(); mTargetDisplacement = displacement; if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) { return; } if (mState != STATE_PULL) { mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY); } mState = STATE_PULL; mStartTime = now; mDuration = PULL_TIME; mPullDistance += deltaDistance; final float absdd = Math.abs(deltaDistance); mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA, mGlowAlpha + (absdd * PULL_DISTANCE_ALPHA_GLOW_FACTOR)); if (mPullDistance == 0) { mGlowScaleY = mGlowScaleYStart = 0; } else { final float scale = (float) (Math.max(0, 1 - 1 / Math.sqrt(Math.abs(mPullDistance) * mBounds.height()) - 0.3d) / 0.7d); mGlowScaleY = mGlowScaleYStart = scale; } mGlowAlphaFinish = mGlowAlpha; mGlowScaleYFinish = mGlowScaleY; } /** * Call when the object is released after being pulled. * This will begin the "decay" phase of the effect. After calling this method * the host view should {@link android.view.View#invalidate()} and thereby * draw the results accordingly. */ public void onRelease() { mPullDistance = 0; if (mState != STATE_PULL && mState != STATE_PULL_DECAY) { return; } mState = STATE_RECEDE; mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; mGlowAlphaFinish = 0.f; mGlowScaleYFinish = 0.f; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = RECEDE_TIME; } /** * Call when the effect absorbs an impact at the given velocity. * Used when a fling reaches the scroll boundary. * * <p>When using a {@link android.widget.Scroller} or {@link android.widget.OverScroller}, * the method <code>getCurrVelocity</code> will provide a reasonable approximation * to use here.</p> * * @param velocity Velocity at impact in pixels per second. */ public void onAbsorb(int velocity) { mState = STATE_ABSORB; velocity = Math.min(Math.max(MIN_VELOCITY, Math.abs(velocity)), MAX_VELOCITY); mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = 0.15f + (velocity * 0.02f); // The glow depends more on the velocity, and therefore starts out // nearly invisible. mGlowAlphaStart = 0.3f; mGlowScaleYStart = Math.max(mGlowScaleY, 0.f); // Growth for the size of the glow should be quadratic to properly // respond // to a user's scrolling speed. The faster the scrolling speed, the more // intense the effect should be for both the size and the saturation. mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f) / 2, 1.f); // Alpha should change for the glow as well as size. mGlowAlphaFinish = Math.max( mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA)); mTargetDisplacement = 0.5f; } /** * Set the color of this edge effect in argb. * * @param color Color in argb */ public void setColor(int color) { mPaint.setColor(color); } /** * Return the color of this edge effect in argb. * @return The color of this edge effect in argb */ public int getColor() { return mPaint.getColor(); } /** * Draw into the provided canvas. Assumes that the canvas has been rotated * accordingly and the size has been set. The effect will be drawn the full * width of X=0 to X=width, beginning from Y=0 and extending to some factor < * 1.f of height. * * @param canvas Canvas to draw into * @return true if drawing should continue beyond this frame to continue the * animation */ public boolean draw(Canvas canvas) { update(); final float centerX = mBounds.centerX(); final float centerY = mBounds.height() - mRadius; canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0); final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f; float translateX = mBounds.width() * displacement / 2; mPaint.setAlpha((int) (0xff * mGlowAlpha)); canvas.drawCircle(centerX + translateX, centerY, mRadius, mPaint); boolean oneLastFrame = false; if (mState == STATE_RECEDE && mGlowScaleY == 0) { mState = STATE_IDLE; oneLastFrame = true; } return mState != STATE_IDLE || oneLastFrame; } /** * Return the maximum height that the edge effect will be drawn at given the original * {@link #setSize(int, int) input size}. * @return The maximum height of the edge effect */ public int getMaxHeight() { return (int) (mBounds.height() * MAX_GLOW_SCALE + 0.5f); } private void update() { final long time = AnimationUtils.currentAnimationTimeMillis(); final float t = Math.min((time - mStartTime) / mDuration, 1.f); final float interp = mInterpolator.getInterpolation(t); mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp; mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp; mDisplacement = (mDisplacement + mTargetDisplacement) / 2; if (t >= 1.f - EPSILON) { switch (mState) { case STATE_ABSORB: mState = STATE_RECEDE; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = RECEDE_TIME; mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; // After absorb, the glow should fade to nothing. mGlowAlphaFinish = 0.f; mGlowScaleYFinish = 0.f; break; case STATE_PULL: mState = STATE_PULL_DECAY; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = PULL_DECAY_TIME; mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; // After pull, the glow should fade to nothing. mGlowAlphaFinish = 0.f; mGlowScaleYFinish = 0.f; break; case STATE_PULL_DECAY: mState = STATE_RECEDE; break; case STATE_RECEDE: mState = STATE_IDLE; break; } } } }