blob: e5c55b0a5e7ff0f36e51039cc1ef3ba7620db6e5 [file] [log] [blame]
/*
* Copyright (C) 2021 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.systemui.wallet.ui;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.MathUtils;
import android.view.View;
import androidx.annotation.ColorInt;
import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.RecyclerView;
import com.android.systemui.R;
final class DotIndicatorDecoration extends RecyclerView.ItemDecoration {
private final int mUnselectedRadius;
private final int mSelectedRadius;
private final int mDotMargin;
@ColorInt private final int mUnselectedColor;
@ColorInt private final int mSelectedColor;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private WalletCardCarousel mCardCarousel;
DotIndicatorDecoration(Context context) {
super();
mUnselectedRadius =
context.getResources().getDimensionPixelSize(
R.dimen.card_carousel_dot_unselected_radius);
mSelectedRadius =
context.getResources().getDimensionPixelSize(
R.dimen.card_carousel_dot_selected_radius);
mDotMargin = context.getResources().getDimensionPixelSize(R.dimen.card_carousel_dot_margin);
mUnselectedColor = context.getColor(
com.google.android.material.R.color.material_dynamic_neutral70);
mSelectedColor = context.getColor(
com.google.android.material.R.color.material_dynamic_neutral100);
}
@Override
public void getItemOffsets(
Rect rect, View view, RecyclerView recyclerView, RecyclerView.State state) {
super.getItemOffsets(rect, view, recyclerView, state);
if (recyclerView.getAdapter().getItemCount() > 1) {
rect.bottom =
view.getResources().getDimensionPixelSize(R.dimen.card_carousel_dot_offset);
}
}
@Override
public void onDrawOver(Canvas canvas, RecyclerView recyclerView, RecyclerView.State state) {
super.onDrawOver(canvas, recyclerView, state);
mCardCarousel = (WalletCardCarousel) recyclerView;
int itemCount = recyclerView.getAdapter().getItemCount();
if (itemCount <= 1) {
// Only shown if there are at least 2 items, and it's not a shimmer loader
return;
}
canvas.save();
float animationStartOffset = recyclerView.getWidth() / 6f;
// 0 when a card is still very prominent, ie. edgeToCenterDistance is greater than
// animationStartOffset
// 1 when the two cards are equidistant from the center ie. edgeToCenterDistance == 0
float interpolatedProgress =
1 - Math.min(Math.abs(mCardCarousel.mEdgeToCenterDistance), animationStartOffset)
/ animationStartOffset;
float totalWidth =
mDotMargin * (itemCount - 1)
+ 2 * mUnselectedRadius * (itemCount - 2)
+ 2 * mSelectedRadius;
// Translate the canvas so the drawing can always start at (0, 0) coordinates.
canvas.translate(
(recyclerView.getWidth() - totalWidth) / 2f,
recyclerView.getHeight() - mDotMargin);
int itemsDrawn = 0;
while (itemsDrawn < itemCount) {
// count up from 0 to itemCount - 1 if LTR; count down from itemCount - 1 to 0 if RTL.
int i = isLayoutLtr() ? itemsDrawn : itemCount - itemsDrawn - 1;
if (isSelectedItem(i)) {
drawSelectedDot(canvas, interpolatedProgress);
} else if (isNextItemInScrollingDirection(i)) {
drawFadingUnselectedDot(canvas, interpolatedProgress);
} else {
drawUnselectedDot(canvas);
}
canvas.translate(mDotMargin, 0);
itemsDrawn++;
}
canvas.restore();
this.mCardCarousel = null; // No need to hold a reference.
}
private void drawSelectedDot(Canvas canvas, float progress) {
// Divide progress by 2 because the other half of the animation is done by
// drawFadingUnselectedDot.
mPaint.setColor(
getTransitionAdjustedColor(
ColorUtils.blendARGB(mSelectedColor, mUnselectedColor, progress / 2)));
float radius = MathUtils.lerp(mSelectedRadius, mUnselectedRadius, progress / 2);
canvas.drawCircle(radius, 0, radius, mPaint);
canvas.translate(radius * 2, 0);
}
private void drawFadingUnselectedDot(Canvas canvas, float progress) {
// Divide progress by 2 because the first half of the animation is done by drawSelectedDot.
int blendedColor =
ColorUtils.blendARGB(
mUnselectedColor, mSelectedColor, progress / 2);
mPaint.setColor(getTransitionAdjustedColor(blendedColor));
float radius = MathUtils.lerp(mUnselectedRadius, mSelectedColor, progress / 2);
canvas.drawCircle(radius, 0, radius, mPaint);
canvas.translate(radius * 2, 0);
}
private void drawUnselectedDot(Canvas canvas) {
mPaint.setColor(mUnselectedColor);
canvas.drawCircle(mUnselectedRadius, 0, mUnselectedRadius, mPaint);
canvas.translate(mUnselectedRadius * 2, 0);
}
private int getTransitionAdjustedColor(int color) {
int transitionAlphaOverride = 0xff;
return ColorUtils.setAlphaComponent(color, transitionAlphaOverride);
}
private boolean isSelectedItem(int position) {
return mCardCarousel.mCenteredAdapterPosition == position;
}
private boolean isNextItemInScrollingDirection(int position) {
if (isLayoutLtr()) {
return (mCardCarousel.mCenteredAdapterPosition + 1 == position
&& mCardCarousel.mEdgeToCenterDistance >= 0f)
|| (mCardCarousel.mCenteredAdapterPosition - 1 == position
&& mCardCarousel.mEdgeToCenterDistance < 0f);
}
return (mCardCarousel.mCenteredAdapterPosition - 1 == position
&& mCardCarousel.mEdgeToCenterDistance >= 0f)
|| (mCardCarousel.mCenteredAdapterPosition + 1 == position
&& mCardCarousel.mEdgeToCenterDistance < 0f);
}
private boolean isLayoutLtr() {
if (mCardCarousel == null) {
// Shouldn't happen, but assume LTR for now.
return true;
}
return mCardCarousel.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
}
}