pubiqq bc2fdeadaf [Internal] Prefer platform methods to compat ones
Resolves https://github.com/material-components/material-components-android/pull/4532

GIT_ORIGIN_REV_ID=180dec736f2521579e483317a4d537629d8f247b
PiperOrigin-RevId: 715494344
2025-01-15 18:06:07 +00:00

184 lines
7.6 KiB
Java

/*
* 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.google.android.material.motion;
import com.google.android.material.R;
import android.animation.TimeInterpolator;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.TypedValue;
import android.view.animation.AnimationUtils;
import android.view.animation.PathInterpolator;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.core.graphics.PathParser;
import androidx.dynamicanimation.animation.SpringForce;
import com.google.android.material.resources.MaterialAttributes;
/** A utility class for motion system functions. */
public class MotionUtils {
// Constants corresponding to motionEasing* theme attr values.
private static final String EASING_TYPE_CUBIC_BEZIER = "cubic-bezier";
private static final String EASING_TYPE_PATH = "path";
private static final String EASING_TYPE_FORMAT_START = "(";
private static final String EASING_TYPE_FORMAT_END = ")";
private MotionUtils() {}
/**
* Resolve a {@link SpringForce} object from a Material spring theme attribute.
*
* @param context the context from where the theme attribute will be resolved
* @param attrResId the {@code motionSpring*} theme attribute to resolve
* into a {@link SpringForce} object
* @param defStyleRes a {@code MaterialSpring} style to load if attrResId cannot be resolved
* @return a {@link SpringForce} object configured using the stiffness and damping from the
* resolved Material spring attribute
*/
@NonNull
public static SpringForce resolveThemeSpringForce(
@NonNull Context context, @AttrRes int attrResId, @StyleRes int defStyleRes) {
TypedValue tv = MaterialAttributes.resolve(context, attrResId);
TypedArray a;
if (tv == null) {
a = context.obtainStyledAttributes(null, R.styleable.MaterialSpring, 0, defStyleRes);
} else {
a = context.obtainStyledAttributes(tv.resourceId, R.styleable.MaterialSpring);
}
SpringForce springForce = new SpringForce();
try {
float stiffness = a.getFloat(R.styleable.MaterialSpring_stiffness, Float.MIN_VALUE);
if (stiffness == Float.MIN_VALUE) {
throw new IllegalArgumentException("A MaterialSpring style must have stiffness value.");
}
float damping = a.getFloat(R.styleable.MaterialSpring_damping, Float.MIN_VALUE);
if (damping == Float.MIN_VALUE) {
throw new IllegalArgumentException("A MaterialSpring style must have a damping value.");
}
springForce.setStiffness(stiffness);
springForce.setDampingRatio(damping);
} finally {
a.recycle();
}
return springForce;
}
/**
* Resolve a duration from a material duration theme attribute.
*
* @param context the context from where the theme attribute will be resolved.
* @param attrResId the {@code motionDuration*} theme attribute to resolve
* @param defaultDuration the duration to be returned if unable to resolve {@code attrResId}
* @return the resolved {@code int} duration which {@code attrResId} points to or the {@code
* defaultDuration} if resolution was unsuccessful.
*/
public static int resolveThemeDuration(
@NonNull Context context, @AttrRes int attrResId, int defaultDuration) {
return MaterialAttributes.resolveInteger(context, attrResId, defaultDuration);
}
/**
* Load an interpolator from a material easing theme attribute.
*
* @param context context from where the theme attribute will be resolved
* @param attrResId the {@code motionEasing*} theme attribute to resolve
* @param defaultInterpolator the interpolator to be returned if unable to resolve {@code
* attrResId}.
* @return the resolved {@link TimeInterpolator} which {@code attrResId} points to or the {@code
* defaultInterpolator} if resolution was unsuccessful.
*/
@NonNull
public static TimeInterpolator resolveThemeInterpolator(
@NonNull Context context,
@AttrRes int attrResId,
@NonNull TimeInterpolator defaultInterpolator) {
TypedValue easingValue = new TypedValue();
if (!context.getTheme().resolveAttribute(attrResId, easingValue, true)) {
return defaultInterpolator;
}
if (easingValue.type != TypedValue.TYPE_STRING) {
throw new IllegalArgumentException(
"Motion easing theme attribute must be an @interpolator resource for"
+ " ?attr/motionEasing*Interpolator attributes or a string for"
+ " ?attr/motionEasing* attributes.");
}
String easingString = String.valueOf(easingValue.string);
if (isLegacyEasingAttribute(easingString)) {
return getLegacyThemeInterpolator(easingString);
}
return AnimationUtils.loadInterpolator(context, easingValue.resourceId);
}
private static TimeInterpolator getLegacyThemeInterpolator(String easingString) {
if (isLegacyEasingType(easingString, EASING_TYPE_CUBIC_BEZIER)) {
String controlPointsString = getLegacyEasingContent(easingString, EASING_TYPE_CUBIC_BEZIER);
String[] controlPoints = controlPointsString.split(",");
if (controlPoints.length != 4) {
throw new IllegalArgumentException(
"Motion easing theme attribute must have 4 control points if using bezier curve"
+ " format; instead got: "
+ controlPoints.length);
}
float controlX1 = getLegacyControlPoint(controlPoints, 0);
float controlY1 = getLegacyControlPoint(controlPoints, 1);
float controlX2 = getLegacyControlPoint(controlPoints, 2);
float controlY2 = getLegacyControlPoint(controlPoints, 3);
return new PathInterpolator(controlX1, controlY1, controlX2, controlY2);
} else if (isLegacyEasingType(easingString, EASING_TYPE_PATH)) {
String path = getLegacyEasingContent(easingString, EASING_TYPE_PATH);
return new PathInterpolator(PathParser.createPathFromPathData(path));
} else {
throw new IllegalArgumentException("Invalid motion easing type: " + easingString);
}
}
private static boolean isLegacyEasingAttribute(String easingString) {
return isLegacyEasingType(easingString, EASING_TYPE_CUBIC_BEZIER)
|| isLegacyEasingType(easingString, EASING_TYPE_PATH);
}
private static boolean isLegacyEasingType(String easingString, String easingType) {
return easingString.startsWith(easingType + EASING_TYPE_FORMAT_START)
&& easingString.endsWith(EASING_TYPE_FORMAT_END);
}
private static String getLegacyEasingContent(String easingString, String easingType) {
return easingString.substring(
easingType.length() + EASING_TYPE_FORMAT_START.length(),
easingString.length() - EASING_TYPE_FORMAT_END.length());
}
private static float getLegacyControlPoint(String[] controlPoints, int index) {
float controlPoint = Float.parseFloat(controlPoints[index]);
if (controlPoint < 0 || controlPoint > 1) {
throw new IllegalArgumentException(
"Motion easing control point value must be between 0 and 1; instead got: "
+ controlPoint);
}
return controlPoint;
}
}