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,