[MaterialCardView] Add fade animation for checked icon drawable

PiperOrigin-RevId: 471663189
This commit is contained in:
rightnao 2022-09-01 16:25:21 -07:00 committed by imhappi
parent 9df6c9d072
commit 7d129eae21
2 changed files with 53 additions and 2 deletions

View File

@ -477,7 +477,7 @@ public class MaterialCardView extends CardView implements Checkable, Shapeable {
checked = !checked;
refreshDrawableState();
forceRippleRedrawIfNeeded();
cardViewHelper.setChecked(checked);
cardViewHelper.setChecked(checked, /* animate= */ true);
if (onCheckedChangeListener != null) {
onCheckedChangeListener.onCheckedChanged(this, checked);
}

View File

@ -21,6 +21,8 @@ import com.google.android.material.R;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static com.google.android.material.card.MaterialCardView.CHECKED_ICON_GRAVITY_TOP_END;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
@ -48,8 +50,10 @@ import androidx.annotation.StyleRes;
import androidx.cardview.widget.CardView;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.ViewCompat;
import com.google.android.material.animation.AnimationUtils;
import com.google.android.material.card.MaterialCardView.CheckedIconGravity;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.motion.MotionUtils;
import com.google.android.material.resources.MaterialResources;
import com.google.android.material.ripple.RippleUtils;
import com.google.android.material.shape.CornerTreatment;
@ -122,6 +126,13 @@ class MaterialCardViewHelper {
private boolean isBackgroundOverwritten = false;
private boolean checkable;
@Nullable private ValueAnimator iconAnimator;
private final TimeInterpolator iconFadeAnimInterpolator;
private final int iconFadeAnimDuration;
private float checkedAnimationProgress = 0F;
public static final int DEFAULT_FADE_ANIM_DURATION = 300;
public MaterialCardViewHelper(
@NonNull MaterialCardView card,
AttributeSet attrs,
@ -146,6 +157,15 @@ class MaterialCardViewHelper {
foregroundContentDrawable = new MaterialShapeDrawable();
setShapeAppearanceModel(shapeAppearanceModelBuilder.build());
iconFadeAnimInterpolator =
MotionUtils.resolveThemeInterpolator(
materialCardView.getContext(),
R.attr.motionEasingLinearInterpolator,
AnimationUtils.LINEAR_INTERPOLATOR);
iconFadeAnimDuration =
MotionUtils.resolveThemeDuration(
materialCardView.getContext(), R.attr.motionDurationShort4, DEFAULT_FADE_ANIM_DURATION);
cardViewAttributes.recycle();
}
@ -282,6 +302,28 @@ class MaterialCardViewHelper {
}
}
public void animateCheckedIcon(boolean checked) {
float targetCheckedProgress = checked ? 1F : 0F;
float delta = checked ? 1F - checkedAnimationProgress : checkedAnimationProgress;
if (iconAnimator != null) {
iconAnimator.cancel();
iconAnimator = null;
}
iconAnimator = ValueAnimator.ofFloat(checkedAnimationProgress, targetCheckedProgress);
iconAnimator.addUpdateListener(
animation -> {
float progress = (float) animation.getAnimatedValue();
int alpha = (int) (255F * progress);
checkedIcon.setAlpha(alpha);
checkedAnimationProgress = progress;
});
iconAnimator.setInterpolator(iconFadeAnimInterpolator);
// Cut the total duration if this animation is starting after interrupting an in-progress
// animation.
iconAnimator.setDuration((long) (iconFadeAnimDuration * delta));
iconAnimator.start();
}
void setCornerRadius(float cornerRadius) {
setShapeAppearanceModel(shapeAppearanceModel.withCornerSize(cornerRadius));
fgDrawable.invalidateSelf();
@ -681,8 +723,17 @@ class MaterialCardViewHelper {
}
public void setChecked(boolean checked) {
setChecked(checked, /* animate= */ false);
}
public void setChecked(boolean checked, boolean animate) {
if (checkedIcon != null) {
checkedIcon.setAlpha(checked ? 255 : 0);
if (animate) {
animateCheckedIcon(checked);
} else {
checkedIcon.setAlpha(checked ? 255 : 0);
checkedAnimationProgress = checked ? 1F : 0F;
}
}
}