mirror of
https://github.com/material-components/material-components-android.git
synced 2026-01-16 18:01:42 +08:00
Resolves https://github.com/material-components/material-components-android/pull/4532 GIT_ORIGIN_REV_ID=180dec736f2521579e483317a4d537629d8f247b PiperOrigin-RevId: 715494344
184 lines
7.6 KiB
Java
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;
|
|
}
|
|
}
|