diff --git a/res/values/dimens.xml b/res/values/dimens.xml index c3cb31db5c..7aa709d117 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -307,6 +307,7 @@ 6dp + 4dp 10dp diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java index a691e45097..37f51899aa 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java +++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java @@ -16,6 +16,7 @@ package com.android.launcher3.pageindicators; +import static com.android.launcher3.Flags.enableLauncherVisualRefresh; import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE; import android.animation.Animator; @@ -57,8 +58,8 @@ import com.android.launcher3.util.Themes; public class PageIndicatorDots extends View implements Insettable, PageIndicator { private static final float SHIFT_PER_ANIMATION = 0.5f; - private static final float SHIFT_THRESHOLD = 0.1f; - private static final long ANIMATION_DURATION = 150; + private static final float SHIFT_THRESHOLD = (enableLauncherVisualRefresh() ? 0.5f : 0.2f); + private static final long ANIMATION_DURATION = (enableLauncherVisualRefresh() ? 200 : 150); private static final int PAGINATION_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay(); private static final int PAGINATION_FADE_IN_DURATION = 83; private static final int PAGINATION_FADE_OUT_DURATION = 167; @@ -78,6 +79,7 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator // This value approximately overshoots to 1.5 times the original size. private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f; + // This is used to optimize the onDraw method by not constructing a new RectF each draw. private static final RectF sTempRect = new RectF(); private static final FloatProperty CURRENT_POSITION = @@ -93,7 +95,7 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator obj.invalidate(); obj.invalidateOutline(); } - }; + }; private static final IntProperty PAGINATION_ALPHA = new IntProperty("pagination_alpha") { @@ -111,6 +113,7 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator private final Handler mDelayedPaginationFadeHandler = new Handler(Looper.getMainLooper()); private final float mDotRadius; + private final float mGapWidth; private final float mCircleGap; private final boolean mIsRtl; @@ -130,6 +133,7 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator * 1.0 => Active dot is at position 1 */ private float mCurrentPosition; + private int mLastPosition; private float mFinalPosition; private boolean mIsScrollPaused; @VisibleForTesting @@ -157,7 +161,10 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator mPaginationPaint.setStyle(Style.FILL); mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.pageIndicatorDotColor)); mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2; - mCircleGap = DOT_GAP_FACTOR * mDotRadius; + mGapWidth = getResources().getDimension(R.dimen.page_indicator_gap_width); + mCircleGap = (enableLauncherVisualRefresh()) + ? mDotRadius * 2 + mGapWidth + : DOT_GAP_FACTOR * mDotRadius; setOutlineProvider(new MyOutlineProver()); mIsRtl = Utilities.isRtl(getResources()); } @@ -188,29 +195,40 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator mTotalScroll = totalScroll; - int scrollPerPage = totalScroll / (mNumPages - 1); - int pageToLeft = scrollPerPage == 0 ? 0 : currentScroll / scrollPerPage; - int pageToLeftScroll = pageToLeft * scrollPerPage; - int pageToRightScroll = pageToLeftScroll + scrollPerPage; + if (enableLauncherVisualRefresh()) { + float scrollPerPage = (float) totalScroll / (mNumPages - 1); + float position = currentScroll / scrollPerPage; + animateToPosition(Math.round(position)); - float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage; - if (currentScroll < pageToLeftScroll + scrollThreshold) { - // scroll is within the left page's threshold - animateToPosition(pageToLeft); - if (mShouldAutoHide) { - hideAfterDelay(); - } - } else if (currentScroll > pageToRightScroll - scrollThreshold) { - // scroll is far enough from left page to go to the right page - animateToPosition(pageToLeft + 1); - if (mShouldAutoHide) { + float delta = Math.abs((int) position - position); + if (mShouldAutoHide && (delta < 0.1 || delta > 0.9)) { hideAfterDelay(); } } else { - // scroll is between left and right page - animateToPosition(pageToLeft + SHIFT_PER_ANIMATION); - if (mShouldAutoHide) { - mDelayedPaginationFadeHandler.removeCallbacksAndMessages(null); + int scrollPerPage = totalScroll / (mNumPages - 1); + int pageToLeft = scrollPerPage == 0 ? 0 : currentScroll / scrollPerPage; + int pageToLeftScroll = pageToLeft * scrollPerPage; + int pageToRightScroll = pageToLeftScroll + scrollPerPage; + + float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage; + if (currentScroll < pageToLeftScroll + scrollThreshold) { + // scroll is within the left page's threshold + animateToPosition(pageToLeft); + if (mShouldAutoHide) { + hideAfterDelay(); + } + } else if (currentScroll > pageToRightScroll - scrollThreshold) { + // scroll is far enough from left page to go to the right page + animateToPosition(pageToLeft + 1); + if (mShouldAutoHide) { + hideAfterDelay(); + } + } else { + // scroll is between left and right page + animateToPosition(pageToLeft + SHIFT_PER_ANIMATION); + if (mShouldAutoHide) { + mDelayedPaginationFadeHandler.removeCallbacksAndMessages(null); + } } } } @@ -283,15 +301,23 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator private void animateToPosition(float position) { mFinalPosition = position; - if (Math.abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) { + if (!enableLauncherVisualRefresh() + && Math.abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) { mCurrentPosition = mFinalPosition; } - if (mAnimator == null && Float.compare(mCurrentPosition, mFinalPosition) != 0) { - float positionForThisAnim = mCurrentPosition > mFinalPosition ? - mCurrentPosition - SHIFT_PER_ANIMATION : mCurrentPosition + SHIFT_PER_ANIMATION; + if (mAnimator == null && Float.compare(mCurrentPosition, position) != 0) { + float positionForThisAnim = enableLauncherVisualRefresh() + ? position + : (mCurrentPosition > mFinalPosition + ? mCurrentPosition - SHIFT_PER_ANIMATION + : mCurrentPosition + SHIFT_PER_ANIMATION); mAnimator = ObjectAnimator.ofFloat(this, CURRENT_POSITION, positionForThisAnim); mAnimator.addListener(new AnimationCycleListener()); mAnimator.setDuration(ANIMATION_DURATION); + if (enableLauncherVisualRefresh()) { + mLastPosition = (int) mCurrentPosition; + mAnimator.setInterpolator(new OvershootInterpolator()); + } mAnimator.start(); } } @@ -314,6 +340,7 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator invalidate(); } + // TODO(b/394355070): Verify Folder Entry Animation works correctly with visual updates public void playEntryAnimation() { int count = mEntryAnimationRadiusFactors.length; if (count == 0) { @@ -391,6 +418,7 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // TODO(b/394355070): Verify Folder Entry Animation works correctly with visual updates // Add extra spacing of mDotRadius on all sides so than entry animation could be run. int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ? MeasureSpec.getSize(widthMeasureSpec) : (int) ((mNumPages * 3 + 2) * mDotRadius); @@ -410,17 +438,14 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator return; } - // Draw all page indicators; float circleGap = mCircleGap; - float startX = ((float) getWidth() / 2) - - (mCircleGap * (((float) mNumPages - 1) / 2)) - - mDotRadius; - - float x = startX + mDotRadius; + float x = ((float) getWidth() / 2) - (mCircleGap * ((float) mNumPages - 1) / 2); float y = getHeight() / 2; if (mEntryAnimationRadiusFactors != null) { // During entry animation, only draw the circles + // TODO(b/394355070): Verify Folder Entry Animation works correctly - visual updates + if (mIsRtl) { x = getWidth() - x; circleGap = -circleGap; @@ -432,18 +457,84 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator x += circleGap; } } else { + // Save the current alpha value, so we can reset to it again after drawing the dots int alpha = mPaginationPaint.getAlpha(); - // Here we draw the dots - mPaginationPaint.setAlpha((int) (alpha * DOT_ALPHA_FRACTION)); - for (int i = 0; i < mNumPages; i++) { - canvas.drawCircle(x, y, mDotRadius, mPaginationPaint); - x += circleGap; + if (enableLauncherVisualRefresh()) { + int nonActiveAlpha = (int) (alpha * DOT_ALPHA_FRACTION); + + float diameter = 2 * mDotRadius; + sTempRect.top = y - mDotRadius; + sTempRect.bottom = y + mDotRadius; + sTempRect.left = x - diameter; + + float posDif = Math.abs(mLastPosition - mCurrentPosition); + float boundedPosition = (posDif > 1) + ? Math.round(mCurrentPosition) + : mCurrentPosition; + float bounceProgress = (posDif > 1) ? posDif - 1 : 0; + float bounceAdjustment = Math.abs(mCurrentPosition - boundedPosition) * diameter; + + // Here we draw the dots, one at a time from the left-most dot to the right-most dot + // 1.0 => 000000 000000111111 000000 + // 1.3 => 000000 0000001111 11000000 + // 1.6 => 000000 00000011 1111000000 + // 2.0 => 000000 000000 111111000000 + for (int i = 0; i < mNumPages; i++) { + mPaginationPaint.setAlpha(nonActiveAlpha); + float delta = Math.abs(boundedPosition - i); + if (delta <= SHIFT_THRESHOLD) { + mPaginationPaint.setAlpha(alpha); + } + + // If boundedPosition is 3.3, both 3 and 4 should enter this condition. + // If boundedPosition is 3, only 3 should enter this condition. + if (delta < 1) { + sTempRect.right = sTempRect.left + diameter + ((1 - delta) * diameter); + + // While the animation is shifting the active pagination dots size from + // the previously active one, to the newly active dot, there is no bounce + // adjustment. The bounce happens in the "Overshoot" phase of the animation. + // mLastPosition is used to determine when the currentPosition is just + // leaving the page, or if it is in the overshoot phase. + if (boundedPosition == i && bounceProgress != 0) { + if (mLastPosition < mCurrentPosition) { + sTempRect.left -= bounceAdjustment; + } else { + sTempRect.right += bounceAdjustment; + } + } + } else { + sTempRect.right = sTempRect.left + diameter; + + if (mLastPosition == i && bounceProgress != 0) { + if (mLastPosition > mCurrentPosition) { + sTempRect.left += bounceAdjustment; + } else { + sTempRect.right -= bounceAdjustment; + } + } + } + canvas.drawRoundRect(sTempRect, mDotRadius, mDotRadius, mPaginationPaint); + + // TODO(b/394355070) Verify RTL experience works correctly with visual updates + sTempRect.left = sTempRect.right + mGapWidth; + } + } else { + // Here we draw the dots + mPaginationPaint.setAlpha((int) (alpha * DOT_ALPHA_FRACTION)); + for (int i = 0; i < mNumPages; i++) { + canvas.drawCircle(x, y, mDotRadius, mPaginationPaint); + x += circleGap; + } + + // Here we draw the current page indicator + mPaginationPaint.setAlpha(alpha); + canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint); } - // Here we draw the current page indicator + // Reset the alpha so it doesn't become progressively more transparent each onDraw call mPaginationPaint.setAlpha(alpha); - canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint); } } @@ -499,6 +590,7 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator @Override public void getOutline(View view, Outline outline) { if (mEntryAnimationRadiusFactors == null) { + // TODO(b/394355070): Verify Outline works correctly with visual updates RectF activeRect = getActiveRect(); outline.setRoundRect( (int) activeRect.left,