liftProgressListeners =
+ new LinkedHashSet<>();
private final long liftOnScrollColorDuration;
private final TimeInterpolator liftOnScrollColorInterpolator;
@@ -268,11 +292,14 @@ public AppBarLayout(@NonNull Context context, @Nullable AttributeSet attrs, int
}
}
- liftOnScrollColorDuration = MotionUtils.resolveThemeDuration(context,
- R.attr.motionDurationMedium2,
- getResources().getInteger(R.integer.app_bar_elevation_anim_duration));
- liftOnScrollColorInterpolator = MotionUtils.resolveThemeInterpolator(context,
- R.attr.motionEasingStandardInterpolator, AnimationUtils.LINEAR_INTERPOLATOR);
+ liftOnScrollColorDuration =
+ MotionUtils.resolveThemeDuration(
+ context,
+ R.attr.motionDurationMedium2,
+ getResources().getInteger(R.integer.app_bar_elevation_anim_duration));
+ liftOnScrollColorInterpolator =
+ MotionUtils.resolveThemeInterpolator(
+ context, R.attr.motionEasingStandardInterpolator, AnimationUtils.LINEAR_INTERPOLATOR);
if (a.hasValue(R.styleable.AppBarLayout_expanded)) {
setExpanded(
@@ -336,7 +363,7 @@ private void initializeLiftOnScrollWithColor(
if (statusBarForeground != null
&& statusBarForegroundOriginalColor != null
&& statusBarForegroundOriginalColor.equals(colorSurface)) {
- DrawableCompat.setTint(statusBarForeground, mixedColor);
+ statusBarForeground.setTint(mixedColor);
}
if (!liftOnScrollListeners.isEmpty()) {
@@ -346,6 +373,12 @@ private void initializeLiftOnScrollWithColor(
}
}
}
+
+ if (!liftProgressListeners.isEmpty()) {
+ for (LiftOnScrollProgressListener liftProgressListener : liftProgressListeners) {
+ liftProgressListener.onUpdate(0, mixedColor, liftProgress);
+ }
+ }
};
setBackground(background);
@@ -354,16 +387,21 @@ private void initializeLiftOnScrollWithColor(
private void initializeLiftOnScrollWithElevation(
Context context, MaterialShapeDrawable background) {
background.initializeElevationOverlay(context);
- liftOnScrollColorUpdateListener = valueAnimator -> {
- float elevation = (float) valueAnimator.getAnimatedValue();
- background.setElevation(elevation);
- if (statusBarForeground instanceof MaterialShapeDrawable) {
- ((MaterialShapeDrawable) statusBarForeground).setElevation(elevation);
- }
- for (LiftOnScrollListener liftOnScrollListener : liftOnScrollListeners) {
- liftOnScrollListener.onUpdate(elevation, background.getResolvedTintColor());
- }
- };
+ liftOnScrollColorUpdateListener =
+ valueAnimator -> {
+ float elevation = (float) valueAnimator.getAnimatedValue();
+ background.setElevation(elevation);
+ if (statusBarForeground instanceof MaterialShapeDrawable) {
+ ((MaterialShapeDrawable) statusBarForeground).setElevation(elevation);
+ }
+ for (LiftOnScrollListener liftOnScrollListener : liftOnScrollListeners) {
+ liftOnScrollListener.onUpdate(elevation, background.getResolvedTintColor());
+ }
+ for (LiftOnScrollProgressListener liftProgressListener : liftProgressListeners) {
+ liftProgressListener.onUpdate(
+ elevation, background.getResolvedTintColor(), elevation / appBarElevation);
+ }
+ };
setBackground(background);
}
@@ -411,21 +449,53 @@ public void removeOnOffsetChangedListener(OnOffsetChangedListener listener) {
/**
* Add a {@link LiftOnScrollListener} that will be called when the lift on scroll elevation and
* background color of this {@link AppBarLayout} change.
+ *
+ * @deprecated Use {@link #addLiftOnScrollProgressListener(LiftOnScrollProgressListener)} instead.
*/
+ @Deprecated
public void addLiftOnScrollListener(@NonNull LiftOnScrollListener liftOnScrollListener) {
liftOnScrollListeners.add(liftOnScrollListener);
}
- /** Remove a previously added {@link LiftOnScrollListener}. */
+ /**
+ * Remove a previously added {@link LiftOnScrollListener}.
+ *
+ * @deprecated Use {@link #removeLiftOnScrollProgressListener(LiftOnScrollProgressListener)} instead.
+ */
+ @CanIgnoreReturnValue
+ @Deprecated
public boolean removeLiftOnScrollListener(@NonNull LiftOnScrollListener liftOnScrollListener) {
return liftOnScrollListeners.remove(liftOnScrollListener);
}
- /** Remove all previously added {@link LiftOnScrollListener}s. */
+ /**
+ * Remove all previously added {@link LiftOnScrollListener}s.
+ *
+ * @deprecated Use {@link #clearLiftOnScrollProgressListener()} instead.
+ */
+ @Deprecated
public void clearLiftOnScrollListener() {
liftOnScrollListeners.clear();
}
+ /**
+ * Add a {@link LiftOnScrollProgressListener} that will be called when the lift on scroll progress changes
+ */
+ public void addLiftOnScrollProgressListener(@NonNull LiftOnScrollProgressListener liftProgressListener) {
+ liftProgressListeners.add(liftProgressListener);
+ }
+
+ /** Remove a previously added {@link LiftOnScrollProgressListener}. */
+ @CanIgnoreReturnValue
+ public boolean removeLiftOnScrollProgressListener(@NonNull LiftOnScrollProgressListener liftProgressListener) {
+ return liftProgressListeners.remove(liftProgressListener);
+ }
+
+ /** Remove all previously added {@link LiftOnScrollProgressListener}s. */
+ public void clearLiftOnScrollProgressListener() {
+ liftProgressListeners.clear();
+ }
+
/**
* Set the drawable to use for the status bar foreground drawable. Providing null will disable the
* scrim functionality.
@@ -559,8 +629,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
case MeasureSpec.AT_MOST:
// For AT_MOST, we need to clamp our desired height with the max height
newHeight =
- clamp(
- getMeasuredHeight() + getTopInset(), 0, MeasureSpec.getSize(heightMeasureSpec));
+ clamp(getMeasuredHeight() + getTopInset(), 0, MeasureSpec.getSize(heightMeasureSpec));
break;
case MeasureSpec.UNSPECIFIED:
// For UNSPECIFIED we can use any height so just add the top inset
@@ -634,9 +703,10 @@ private void invalidateScrollRanges() {
// If there's a pending action, we should skip this step and respect the pending action.
SavedState savedState =
behavior == null
- || totalScrollRange == INVALID_SCROLL_RANGE
- || pendingAction != PENDING_ACTION_NONE
- ? null : behavior.saveScrollState(AbsSavedState.EMPTY_STATE, this);
+ || totalScrollRange == INVALID_SCROLL_RANGE
+ || pendingAction != PENDING_ACTION_NONE
+ ? null
+ : behavior.saveScrollState(AbsSavedState.EMPTY_STATE, this);
// Invalidate the scroll ranges
totalScrollRange = INVALID_SCROLL_RANGE;
downPreScrollRange = INVALID_SCROLL_RANGE;
@@ -1040,8 +1110,7 @@ private boolean isLiftOnScrollCompatibleBackground() {
return getBackground() instanceof MaterialShapeDrawable;
}
- private void startLiftOnScrollColorAnimation(
- float fromValue, float toValue) {
+ private void startLiftOnScrollColorAnimation(float fromValue, float toValue) {
if (liftOnScrollColorAnimator != null) {
liftOnScrollColorAnimator.cancel();
}
@@ -1225,6 +1294,7 @@ public static class LayoutParams extends LinearLayout.LayoutParams {
})
@Retention(RetentionPolicy.SOURCE)
public @interface ScrollFlags {}
+
/**
* Disable scrolling on the view. This flag should not be combined with any of the other scroll
* flags.
@@ -1242,7 +1312,7 @@ public static class LayoutParams extends LinearLayout.LayoutParams {
* When exiting (scrolling off screen) the view will be scrolled until it is 'collapsed'. The
* collapsed height is defined by the view's minimum height.
*
- * @see ViewCompat#getMinimumHeight(View)
+ * @see View#getMinimumHeight()
* @see View#setMinimumHeight(int)
*/
public static final int SCROLL_FLAG_EXIT_UNTIL_COLLAPSED = 1 << 1;
@@ -1260,7 +1330,7 @@ public static class LayoutParams extends LinearLayout.LayoutParams {
* scroll range, the remainder of this view will be scrolled into view. The collapsed height is
* defined by the view's minimum height.
*
- * @see ViewCompat#getMinimumHeight(View)
+ * @see View#getMinimumHeight()
* @see View#setMinimumHeight(int)
*/
public static final int SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED = 1 << 3;
@@ -1296,8 +1366,8 @@ public static class LayoutParams extends LinearLayout.LayoutParams {
/**
* An effect that will "compress" this view as it hits the scroll ceiling (typically the top of
- * the screen). This is a parallax effect that masks this view and decreases its scroll ratio
- * in relation to the AppBarLayout's offset.
+ * the screen). This is a parallax effect that masks this view and decreases its scroll ratio in
+ * relation to the AppBarLayout's offset.
*/
public static final int SCROLL_EFFECT_COMPRESS = 1;
@@ -1396,9 +1466,7 @@ private ChildScrollEffect createScrollEffectFromInt(int scrollEffectInt) {
}
}
- /**
- * Get the scroll effect to be applied when the AppBarLayout's offset changes
- */
+ /** Get the scroll effect to be applied when the AppBarLayout's offset changes */
@Nullable
public ChildScrollEffect getScrollEffect() {
return scrollEffect;
@@ -1408,7 +1476,7 @@ public ChildScrollEffect getScrollEffect() {
* Set the scroll effect to be applied when the AppBarLayout's offset changes.
*
* @param scrollEffect An {@code AppBarLayoutChildScrollEffect} implementation. If null is
- * passed, the scroll effect will be cleared and no effect will be applied.
+ * passed, the scroll effect will be cleared and no effect will be applied.
*/
public void setScrollEffect(@Nullable ChildScrollEffect scrollEffect) {
this.scrollEffect = scrollEffect;
@@ -1417,9 +1485,9 @@ public void setScrollEffect(@Nullable ChildScrollEffect scrollEffect) {
/**
* Set the scroll effect to be applied when the AppBarLayout's offset changes.
*
- * @param scrollEffect An {@code AppBarLayoutChildScrollEffect} implementation. If
- * {@link #SCROLL_EFFECT_NONE} is passed, the scroll effect will be cleared and no
- * effect will be applied.
+ * @param scrollEffect An {@code AppBarLayoutChildScrollEffect} implementation. If {@link
+ * #SCROLL_EFFECT_NONE} is passed, the scroll effect will be cleared and no effect will be
+ * applied.
*/
public void setScrollEffect(@ScrollEffect int scrollEffect) {
this.scrollEffect = createScrollEffectFromInt(scrollEffect);
@@ -1816,7 +1884,7 @@ public boolean onLayoutChild(
// Keep fully expanded.
setHeaderTopBottomOffset(parent, abl, 0);
} else {
- // Not fully scrolled, restore the visible percetage of child layout.
+ // Not fully scrolled, restore the visible percentage of child layout.
View child = abl.getChildAt(savedState.firstVisibleChildIndex);
int offset = -child.getBottom();
if (savedState.firstVisibleChildAtMinimumHeight) {
@@ -1850,8 +1918,7 @@ public boolean onLayoutChild(
// We may have changed size, so let's constrain the top and bottom offset correctly,
// just in case we're out of the bounds
- setTopAndBottomOffset(
- clamp(getTopAndBottomOffset(), -abl.getTotalScrollRange(), 0));
+ setTopAndBottomOffset(clamp(getTopAndBottomOffset(), -abl.getTotalScrollRange(), 0));
// Update the AppBarLayout's drawable state for any elevation changes. This is needed so that
// the elevation is set in the first layout, so that we don't get a visual jump pre-N (due to
diff --git a/lib/java/com/google/android/material/appbar/CollapsingToolbarLayout.java b/lib/java/com/google/android/material/appbar/CollapsingToolbarLayout.java
index 6e5b95bb676..9d451aced91 100644
--- a/lib/java/com/google/android/material/appbar/CollapsingToolbarLayout.java
+++ b/lib/java/com/google/android/material/appbar/CollapsingToolbarLayout.java
@@ -53,7 +53,6 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
-import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.math.MathUtils;
import androidx.core.util.ObjectsCompat;
@@ -155,6 +154,27 @@ public class CollapsingToolbarLayout extends FrameLayout {
@Retention(RetentionPolicy.SOURCE)
public @interface TitleCollapseMode {}
+ /**
+ * The gravity of the collapsed title is based on the entire space of the CollapsedToolbarLayout.
+ */
+ private static final int COLLAPSED_TITLE_GRAVITY_ENTIRE_SPACE = 0;
+
+ /**
+ * The gravity of the collapsed title is based on the remaining space in the
+ * CollapsedToolbarLayout after accounting for other views such as the menu.
+ */
+ private static final int COLLAPSED_TITLE_GRAVITY_AVAILABLE_SPACE = 1;
+
+ /**
+ * The mode in which to calculate the gravity of the collapsed title.
+ *
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ @IntDef(value = {COLLAPSED_TITLE_GRAVITY_ENTIRE_SPACE, COLLAPSED_TITLE_GRAVITY_AVAILABLE_SPACE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CollapsedTitleGravityMode {}
+
private boolean refreshToolbar = true;
private int toolbarId;
@Nullable private ViewGroup toolbar;
@@ -165,12 +185,15 @@ public class CollapsingToolbarLayout extends FrameLayout {
private int expandedMarginTop;
private int expandedMarginEnd;
private int expandedMarginBottom;
+ private int expandedTitleSpacing;
private final Rect tmpRect = new Rect();
- @NonNull final CollapsingTextHelper collapsingTextHelper;
+ @NonNull final CollapsingTextHelper collapsingTitleHelper;
+ @NonNull final CollapsingTextHelper collapsingSubtitleHelper;
@NonNull final ElevationOverlayProvider elevationOverlayProvider;
private boolean collapsingTitleEnabled;
private boolean drawCollapsingTitle;
+ @CollapsedTitleGravityMode private final int collapsedTitleGravityMode;
@Nullable private Drawable contentScrim;
@Nullable Drawable statusBarScrim;
@@ -194,9 +217,12 @@ public class CollapsingToolbarLayout extends FrameLayout {
private int topInsetApplied = 0;
private boolean forceApplySystemWindowInsetTop;
- private int extraMultilineHeight = 0;
+ private int extraMultilineTitleHeight = 0;
+ private int extraMultilineSubtitleHeight = 0;
private boolean extraMultilineHeightEnabled;
+ private int extraHeightForTitles = 0;
+
public CollapsingToolbarLayout(@NonNull Context context) {
this(context, null);
}
@@ -205,35 +231,39 @@ public CollapsingToolbarLayout(@NonNull Context context, @Nullable AttributeSet
this(context, attrs, R.attr.collapsingToolbarLayoutStyle);
}
- public CollapsingToolbarLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ public CollapsingToolbarLayout(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(wrap(context, attrs, defStyleAttr, DEF_STYLE_RES), attrs, defStyleAttr);
// Ensure we are using the correctly themed context rather than the context that was passed in.
context = getContext();
screenOrientation = getResources().getConfiguration().orientation;
- collapsingTextHelper = new CollapsingTextHelper(this);
- collapsingTextHelper.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
- collapsingTextHelper.setRtlTextDirectionHeuristicsEnabled(false);
+ collapsingTitleHelper = new CollapsingTextHelper(this);
+ collapsingTitleHelper.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
+ collapsingTitleHelper.setRtlTextDirectionHeuristicsEnabled(false);
elevationOverlayProvider = new ElevationOverlayProvider(context);
TypedArray a =
ThemeEnforcement.obtainStyledAttributes(
- context,
- attrs,
- R.styleable.CollapsingToolbarLayout,
- defStyleAttr,
- DEF_STYLE_RES);
+ context, attrs, R.styleable.CollapsingToolbarLayout, defStyleAttr, DEF_STYLE_RES);
- collapsingTextHelper.setExpandedTextGravity(
+ int titleExpandedGravity =
a.getInt(
R.styleable.CollapsingToolbarLayout_expandedTitleGravity,
- Gravity.START | Gravity.BOTTOM));
- collapsingTextHelper.setCollapsedTextGravity(
+ Gravity.START | Gravity.BOTTOM);
+ int titleCollapsedGravity =
a.getInt(
R.styleable.CollapsingToolbarLayout_collapsedTitleGravity,
- Gravity.START | Gravity.CENTER_VERTICAL));
+ Gravity.START | Gravity.CENTER_VERTICAL);
+ collapsedTitleGravityMode =
+ a.getInt(
+ R.styleable.CollapsingToolbarLayout_collapsedTitleGravityMode,
+ COLLAPSED_TITLE_GRAVITY_AVAILABLE_SPACE);
+
+ collapsingTitleHelper.setExpandedTextGravity(titleExpandedGravity);
+ collapsingTitleHelper.setCollapsedTextGravity(titleCollapsedGravity);
expandedMarginStart =
expandedMarginTop =
@@ -258,23 +288,27 @@ public CollapsingToolbarLayout(@NonNull Context context, @Nullable AttributeSet
expandedMarginBottom =
a.getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom, 0);
}
+ if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleSpacing)) {
+ expandedTitleSpacing =
+ a.getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_expandedTitleSpacing, 0);
+ }
collapsingTitleEnabled = a.getBoolean(R.styleable.CollapsingToolbarLayout_titleEnabled, true);
setTitle(a.getText(R.styleable.CollapsingToolbarLayout_title));
// First load the default text appearances
- collapsingTextHelper.setExpandedTextAppearance(
+ collapsingTitleHelper.setExpandedTextAppearance(
R.style.TextAppearance_Design_CollapsingToolbar_Expanded);
- collapsingTextHelper.setCollapsedTextAppearance(
+ collapsingTitleHelper.setCollapsedTextAppearance(
androidx.appcompat.R.style.TextAppearance_AppCompat_Widget_ActionBar_Title);
// Now overlay any custom text appearances
if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance)) {
- collapsingTextHelper.setExpandedTextAppearance(
+ collapsingTitleHelper.setExpandedTextAppearance(
a.getResourceId(R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance, 0));
}
if (a.hasValue(R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance)) {
- collapsingTextHelper.setCollapsedTextAppearance(
+ collapsingTitleHelper.setCollapsedTextAppearance(
a.getResourceId(R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance, 0));
}
@@ -286,12 +320,12 @@ public CollapsingToolbarLayout(@NonNull Context context, @Nullable AttributeSet
}
if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleTextColor)) {
- collapsingTextHelper.setExpandedTextColor(
+ collapsingTitleHelper.setExpandedTextColor(
MaterialResources.getColorStateList(
context, a, R.styleable.CollapsingToolbarLayout_expandedTitleTextColor));
}
if (a.hasValue(R.styleable.CollapsingToolbarLayout_collapsedTitleTextColor)) {
- collapsingTextHelper.setCollapsedTextColor(
+ collapsingTitleHelper.setCollapsedTextColor(
MaterialResources.getColorStateList(
context, a, R.styleable.CollapsingToolbarLayout_collapsedTitleTextColor));
}
@@ -299,12 +333,59 @@ public CollapsingToolbarLayout(@NonNull Context context, @Nullable AttributeSet
scrimVisibleHeightTrigger =
a.getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_scrimVisibleHeightTrigger, -1);
- if (a.hasValue(R.styleable.CollapsingToolbarLayout_maxLines)) {
- collapsingTextHelper.setExpandedMaxLines(a.getInt(R.styleable.CollapsingToolbarLayout_maxLines, 1));
+ if (a.hasValue(R.styleable.CollapsingToolbarLayout_titleMaxLines)) {
+ collapsingTitleHelper.setExpandedMaxLines(
+ a.getInt(R.styleable.CollapsingToolbarLayout_titleMaxLines, 1));
+ } else if (a.hasValue(R.styleable.CollapsingToolbarLayout_maxLines)) {
+ collapsingTitleHelper.setExpandedMaxLines(
+ a.getInt(R.styleable.CollapsingToolbarLayout_maxLines, 1));
+ }
+
+ if (a.hasValue(R.styleable.CollapsingToolbarLayout_titlePositionInterpolator)) {
+ collapsingTitleHelper.setPositionInterpolator(
+ android.view.animation.AnimationUtils.loadInterpolator(
+ context,
+ a.getResourceId(R.styleable.CollapsingToolbarLayout_titlePositionInterpolator, 0)));
+ }
+
+ collapsingSubtitleHelper = new CollapsingTextHelper(this);
+ collapsingSubtitleHelper.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
+ collapsingSubtitleHelper.setRtlTextDirectionHeuristicsEnabled(false);
+
+ if (a.hasValue(R.styleable.CollapsingToolbarLayout_subtitle)) {
+ setSubtitle(a.getText(R.styleable.CollapsingToolbarLayout_subtitle));
}
+ collapsingSubtitleHelper.setExpandedTextGravity(titleExpandedGravity);
+ collapsingSubtitleHelper.setCollapsedTextGravity(titleCollapsedGravity);
+ collapsingSubtitleHelper.setExpandedTextAppearance(
+ androidx.appcompat.R.style.TextAppearance_AppCompat_Headline);
+ collapsingSubtitleHelper.setCollapsedTextAppearance(
+ androidx.appcompat.R.style.TextAppearance_AppCompat_Widget_ActionBar_Subtitle);
+ if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedSubtitleTextAppearance)) {
+ collapsingSubtitleHelper.setExpandedTextAppearance(
+ a.getResourceId(R.styleable.CollapsingToolbarLayout_expandedSubtitleTextAppearance, 0));
+ }
+ if (a.hasValue(R.styleable.CollapsingToolbarLayout_collapsedSubtitleTextAppearance)) {
+ collapsingSubtitleHelper.setCollapsedTextAppearance(
+ a.getResourceId(R.styleable.CollapsingToolbarLayout_collapsedSubtitleTextAppearance, 0));
+ }
+ if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedSubtitleTextColor)) {
+ collapsingSubtitleHelper.setExpandedTextColor(
+ MaterialResources.getColorStateList(
+ context, a, R.styleable.CollapsingToolbarLayout_expandedSubtitleTextColor));
+ }
+ if (a.hasValue(R.styleable.CollapsingToolbarLayout_collapsedSubtitleTextColor)) {
+ collapsingSubtitleHelper.setCollapsedTextColor(
+ MaterialResources.getColorStateList(
+ context, a, R.styleable.CollapsingToolbarLayout_collapsedSubtitleTextColor));
+ }
+ if (a.hasValue(R.styleable.CollapsingToolbarLayout_subtitleMaxLines)) {
+ collapsingSubtitleHelper.setExpandedMaxLines(
+ a.getInt(R.styleable.CollapsingToolbarLayout_subtitleMaxLines, 1));
+ }
if (a.hasValue(R.styleable.CollapsingToolbarLayout_titlePositionInterpolator)) {
- collapsingTextHelper.setPositionInterpolator(
+ collapsingSubtitleHelper.setPositionInterpolator(
android.view.animation.AnimationUtils.loadInterpolator(
context,
a.getResourceId(R.styleable.CollapsingToolbarLayout_titlePositionInterpolator, 0)));
@@ -374,7 +455,7 @@ protected void onAttachedToWindow() {
appBarLayout.addOnOffsetChangedListener(onOffsetChangedListener);
// We're attached, so lets request an inset dispatch
- ViewCompat.requestApplyInsets(this);
+ requestApplyInsets();
}
}
@@ -426,15 +507,17 @@ public void draw(@NonNull Canvas canvas) {
&& contentScrim != null
&& scrimAlpha > 0
&& isTitleCollapseFadeMode()
- && collapsingTextHelper.getExpansionFraction()
- < collapsingTextHelper.getFadeModeThresholdFraction()) {
+ && collapsingTitleHelper.getExpansionFraction()
+ < collapsingTitleHelper.getFadeModeThresholdFraction()) {
// Mask the expanded text with the contentScrim
int save = canvas.save();
canvas.clipRect(contentScrim.getBounds(), Op.DIFFERENCE);
- collapsingTextHelper.draw(canvas);
+ collapsingTitleHelper.draw(canvas);
+ collapsingSubtitleHelper.draw(canvas);
canvas.restoreToCount(save);
} else {
- collapsingTextHelper.draw(canvas);
+ collapsingTitleHelper.draw(canvas);
+ collapsingSubtitleHelper.draw(canvas);
}
}
@@ -452,7 +535,7 @@ && isTitleCollapseFadeMode()
@Override
protected void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- collapsingTextHelper.maybeUpdateFontWeightAdjustment(newConfig);
+ collapsingTitleHelper.maybeUpdateFontWeightAdjustment(newConfig);
// When the orientation changes with extra multiline height enabled and when collapsed, there
// can be an issue where the offset/scroll state is invalid due to the number of lines of text
@@ -461,7 +544,7 @@ protected void onConfigurationChanged(@NonNull Configuration newConfig) {
// fully collapsed prior to screen rotation.
if (screenOrientation != newConfig.orientation
&& extraMultilineHeightEnabled
- && collapsingTextHelper.getExpansionFraction() == 1f) {
+ && collapsingTitleHelper.getExpansionFraction() == 1f) {
ViewParent parent = getParent();
if (parent instanceof AppBarLayout) {
AppBarLayout appBarLayout = (AppBarLayout) parent;
@@ -616,22 +699,67 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
- if (extraMultilineHeightEnabled && collapsingTextHelper.getExpandedMaxLines() > 1) {
+ updateTitleFromToolbarIfNeeded();
+
+ if (collapsingTitleEnabled && !TextUtils.isEmpty(collapsingTitleHelper.getText())) {
+ final int originalHeight = getMeasuredHeight();
// Need to update title and bounds in order to calculate line count and text height.
- updateTitleFromToolbarIfNeeded();
- updateTextBounds(0, 0, getMeasuredWidth(), getMeasuredHeight(), /* forceRecalculate= */ true);
-
- int lineCount = collapsingTextHelper.getExpandedLineCount();
- if (lineCount > 1) {
- // Add extra height based on the amount of height beyond the first line of title text.
- int expandedTextHeight =
- Math.round(collapsingTextHelper.getExpandedTextFullSingleLineHeight());
- extraMultilineHeight = expandedTextHeight * (lineCount - 1);
- int newHeight = getMeasuredHeight() + extraMultilineHeight;
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY);
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ updateTextBounds(0, 0, getMeasuredWidth(), originalHeight, /* forceRecalculate= */ true);
+
+ // Calculates the extra height needed for the contents of the collapsing toolbar, if needed.
+ int expectedHeight =
+ (int)
+ (topInsetApplied
+ + expandedMarginTop
+ + collapsingTitleHelper.getExpandedTextFullSingleLineHeight()
+ + (TextUtils.isEmpty(collapsingSubtitleHelper.getText())
+ ? 0
+ : expandedTitleSpacing
+ + collapsingSubtitleHelper.getExpandedTextFullSingleLineHeight())
+ + expandedMarginBottom);
+ if (expectedHeight > originalHeight) {
+ extraHeightForTitles = expectedHeight - originalHeight;
} else {
- extraMultilineHeight = 0;
+ extraHeightForTitles = 0;
+ }
+
+ if (extraMultilineHeightEnabled) {
+ // Calculates the extra height needed for the multiline title, if needed.
+ if (collapsingTitleHelper.getExpandedMaxLines() > 1) {
+ int lineCount = collapsingTitleHelper.getExpandedLineCount();
+ if (lineCount > 1) {
+ // Add extra height based on the amount of height beyond the first line of title text.
+ int expandedTextHeight =
+ Math.round(collapsingTitleHelper.getExpandedTextFullSingleLineHeight());
+ extraMultilineTitleHeight = expandedTextHeight * (lineCount - 1);
+ } else {
+ extraMultilineTitleHeight = 0;
+ }
+ }
+ // Calculates the extra height needed for the multiline subtitle, if needed.
+ if (collapsingSubtitleHelper.getExpandedMaxLines() > 1) {
+ int lineCount = collapsingSubtitleHelper.getExpandedLineCount();
+ if (lineCount > 1) {
+ // Add extra height based on the amount of height beyond the first line of subtitle
+ // text.
+ int expandedTextHeight =
+ Math.round(collapsingSubtitleHelper.getExpandedTextFullSingleLineHeight());
+ extraMultilineSubtitleHeight = expandedTextHeight * (lineCount - 1);
+ } else {
+ extraMultilineSubtitleHeight = 0;
+ }
+ }
+ }
+
+ if (extraHeightForTitles + extraMultilineTitleHeight + extraMultilineSubtitleHeight > 0) {
+ heightMeasureSpec =
+ MeasureSpec.makeMeasureSpec(
+ originalHeight
+ + extraHeightForTitles
+ + extraMultilineTitleHeight
+ + extraMultilineSubtitleHeight,
+ MeasureSpec.EXACTLY);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@@ -669,7 +797,7 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
getViewOffsetHelper(getChildAt(i)).onViewLayout();
}
- updateTextBounds(left, top, right, bottom, /* forceRecalculate= */false);
+ updateTextBounds(left, top, right, bottom, /* forceRecalculate= */ false);
updateTitleFromToolbarIfNeeded();
@@ -696,23 +824,60 @@ private void updateTextBounds(
updateCollapsedBounds(isRtl);
// Update the expanded bounds
- collapsingTextHelper.setExpandedBounds(
- isRtl ? expandedMarginEnd : expandedMarginStart,
- tmpRect.top + expandedMarginTop,
- right - left - (isRtl ? expandedMarginStart : expandedMarginEnd),
- bottom - top - expandedMarginBottom);
-
- // Now recalculate using the new bounds
- collapsingTextHelper.recalculate(forceRecalculate);
+ final int titleBoundsLeft = isRtl ? expandedMarginEnd : expandedMarginStart;
+ final int titleBoundsTop = tmpRect.top + expandedMarginTop;
+ final int titleBoundsRight =
+ right - left - (isRtl ? expandedMarginStart : expandedMarginEnd);
+ final int titleBoundsBottom = bottom - top - expandedMarginBottom;
+ if (TextUtils.isEmpty(collapsingSubtitleHelper.getText())) {
+ collapsingTitleHelper.setExpandedBounds(
+ titleBoundsLeft, titleBoundsTop, titleBoundsRight, titleBoundsBottom);
+
+ // Now recalculate using the new bounds
+ collapsingTitleHelper.recalculate(forceRecalculate);
+ } else {
+ collapsingTitleHelper.setExpandedBounds(
+ titleBoundsLeft,
+ titleBoundsTop,
+ titleBoundsRight,
+ (int)
+ (titleBoundsBottom
+ - (collapsingSubtitleHelper.getExpandedTextFullSingleLineHeight()
+ + extraMultilineSubtitleHeight)
+ - expandedTitleSpacing),
+ /* alignBaselineAtBottom= */ false);
+ collapsingSubtitleHelper.setExpandedBounds(
+ titleBoundsLeft,
+ (int)
+ (titleBoundsTop
+ + (collapsingTitleHelper.getExpandedTextFullSingleLineHeight()
+ + extraMultilineTitleHeight)
+ + expandedTitleSpacing),
+ titleBoundsRight,
+ titleBoundsBottom,
+ /* alignBaselineAtBottom= */ false);
+
+ // Now recalculate using the new bounds
+ collapsingTitleHelper.recalculate(forceRecalculate);
+ collapsingSubtitleHelper.recalculate(forceRecalculate);
+ }
}
}
}
private void updateTitleFromToolbarIfNeeded() {
if (toolbar != null) {
- if (collapsingTitleEnabled && TextUtils.isEmpty(collapsingTextHelper.getText())) {
- // If we do not currently have a title, try and grab it from the Toolbar
- setTitle(getToolbarTitle(toolbar));
+ if (collapsingTitleEnabled) {
+ CharSequence title = getToolbarTitle(toolbar);
+ if (TextUtils.isEmpty(collapsingTitleHelper.getText()) && !TextUtils.isEmpty(title)) {
+ // If we do not currently have a title, try and grab it from the Toolbar
+ setTitle(title);
+ }
+ CharSequence subtitle = getToolbarSubtitle(toolbar);
+ if (TextUtils.isEmpty(collapsingSubtitleHelper.getText()) && !TextUtils.isEmpty(subtitle)) {
+ // If we do not currently have a subtitle, try and grab it from the Toolbar
+ setSubtitle(subtitle);
+ }
}
}
}
@@ -743,13 +908,56 @@ private void updateCollapsedBounds(boolean isRtl) {
titleMarginTop = 0;
titleMarginBottom = 0;
}
- collapsingTextHelper.setCollapsedBounds(
- tmpRect.left + (isRtl ? titleMarginEnd : titleMarginStart),
- tmpRect.top + maxOffset + titleMarginTop,
- tmpRect.right - (isRtl ? titleMarginStart : titleMarginEnd),
- tmpRect.bottom + maxOffset - titleMarginBottom);
+ final int titleBoundsLeft = tmpRect.left + (isRtl ? titleMarginEnd : titleMarginStart);
+ final int titleBoundsRight = tmpRect.right - (isRtl ? titleMarginStart : titleMarginEnd);
+ final int titleBoundsTop = tmpRect.top + maxOffset + titleMarginTop;
+ final int titleBoundsBottom = tmpRect.bottom + maxOffset - titleMarginBottom;
+ final int titleBoundsBottomWithSubtitle =
+ (int) (titleBoundsBottom - collapsingSubtitleHelper.getCollapsedFullSingleLineHeight());
+ final int subtitleBoundsTop = (int) (titleBoundsTop + collapsingTitleHelper.getCollapsedFullSingleLineHeight());
+
+ // Setting the valid collapsed bounds that text can be displayed in
+ if (TextUtils.isEmpty(collapsingSubtitleHelper.getText())) {
+ collapsingTitleHelper.setCollapsedBounds(
+ titleBoundsLeft, titleBoundsTop, titleBoundsRight, titleBoundsBottom);
+ } else {
+ collapsingTitleHelper.setCollapsedBounds(
+ titleBoundsLeft,
+ titleBoundsTop,
+ titleBoundsRight,
+ titleBoundsBottomWithSubtitle);
+ collapsingSubtitleHelper.setCollapsedBounds(
+ titleBoundsLeft,
+ subtitleBoundsTop,
+ titleBoundsRight,
+ titleBoundsBottom);
+ }
+
+ // If the collapsed title gravity should be using the whole collapsing toolbar layout instead of
+ // the dummy layout, we should set the collapsed bounds for offsets.
+ if (collapsedTitleGravityMode == COLLAPSED_TITLE_GRAVITY_ENTIRE_SPACE) {
+ DescendantOffsetUtils.getDescendantRect(this, this, tmpRect);
+ final int validTitleBoundsLeft = tmpRect.left + (isRtl ? titleMarginEnd : titleMarginStart);
+ final int validTitleBoundsRight = tmpRect.right - (isRtl ? titleMarginStart : titleMarginEnd);
+ if (TextUtils.isEmpty(collapsingSubtitleHelper.getText())) {
+ collapsingTitleHelper.setCollapsedBoundsForOffsets(
+ validTitleBoundsLeft, titleBoundsTop, validTitleBoundsRight, titleBoundsBottom);
+ } else {
+ collapsingTitleHelper.setCollapsedBoundsForOffsets(
+ validTitleBoundsLeft,
+ titleBoundsTop,
+ validTitleBoundsRight,
+ titleBoundsBottomWithSubtitle);
+ collapsingSubtitleHelper.setCollapsedBoundsForOffsets(
+ validTitleBoundsLeft,
+ subtitleBoundsTop,
+ validTitleBoundsRight,
+ titleBoundsBottom);
+ }
+ }
}
+ @Nullable
private static CharSequence getToolbarTitle(View view) {
if (view instanceof androidx.appcompat.widget.Toolbar) {
return ((androidx.appcompat.widget.Toolbar) view).getTitle();
@@ -760,6 +968,17 @@ private static CharSequence getToolbarTitle(View view) {
}
}
+ @Nullable
+ private static CharSequence getToolbarSubtitle(View view) {
+ if (view instanceof androidx.appcompat.widget.Toolbar) {
+ return ((androidx.appcompat.widget.Toolbar) view).getSubtitle();
+ } else if (view instanceof android.widget.Toolbar) {
+ return ((android.widget.Toolbar) view).getSubtitle();
+ } else {
+ return null;
+ }
+ }
+
private static int getHeightWithMargins(@NonNull final View view) {
final ViewGroup.LayoutParams lp = view.getLayoutParams();
if (lp instanceof MarginLayoutParams) {
@@ -787,7 +1006,7 @@ static ViewOffsetHelper getViewOffsetHelper(@NonNull View view) {
* @attr ref R.styleable#CollapsingToolbarLayout_title
*/
public void setTitle(@Nullable CharSequence title) {
- collapsingTextHelper.setText(title);
+ collapsingTitleHelper.setText(title);
updateContentDescriptionFromTitle();
}
@@ -799,7 +1018,29 @@ public void setTitle(@Nullable CharSequence title) {
*/
@Nullable
public CharSequence getTitle() {
- return collapsingTitleEnabled ? collapsingTextHelper.getText() : null;
+ return collapsingTitleEnabled ? collapsingTitleHelper.getText() : null;
+ }
+
+ /**
+ * Sets the subtitle to be displayed by this view, if enabled.
+ *
+ * @see #setTitleEnabled(boolean)
+ * @see #getSubtitle()
+ * @attr ref R.styleable#CollapsingToolbarLayout_subtitle
+ */
+ public void setSubtitle(@Nullable CharSequence subtitle) {
+ collapsingSubtitleHelper.setText(subtitle);
+ }
+
+ /**
+ * Returns the subtitle currently being displayed by this view. If the subtitle is not enabled,
+ * then this will return {@code null}.
+ *
+ * @attr ref R.styleable#CollapsingToolbarLayout_subtitle
+ */
+ @Nullable
+ public CharSequence getSubtitle() {
+ return collapsingTitleEnabled ? collapsingSubtitleHelper.getText() : null;
}
/**
@@ -812,7 +1053,8 @@ public void setTitleCollapseMode(@TitleCollapseMode int titleCollapseMode) {
this.titleCollapseMode = titleCollapseMode;
boolean fadeModeEnabled = isTitleCollapseFadeMode();
- collapsingTextHelper.setFadeModeEnabled(fadeModeEnabled);
+ collapsingTitleHelper.setFadeModeEnabled(fadeModeEnabled);
+ collapsingSubtitleHelper.setFadeModeEnabled(fadeModeEnabled);
ViewParent parent = getParent();
if (parent instanceof AppBarLayout) {
@@ -876,7 +1118,6 @@ public boolean isTitleEnabled() {
return collapsingTitleEnabled;
}
-
/**
* Set ellipsizing on the title text.
*
@@ -884,15 +1125,13 @@ public boolean isTitleEnabled() {
* @attr ref R.styleable#CollapsingToolbarLayout_titleTextEllipsize
*/
public void setTitleEllipsize(@NonNull TruncateAt ellipsize) {
- collapsingTextHelper.setTitleTextEllipsize(ellipsize);
+ collapsingTitleHelper.setTitleTextEllipsize(ellipsize);
}
- /**
- * Get ellipsizing currently applied on the title text.
- */
+ /** Get ellipsizing currently applied on the title text. */
@NonNull
public TruncateAt getTitleTextEllipsize() {
- return collapsingTextHelper.getTitleTextEllipsize();
+ return collapsingTitleHelper.getTitleTextEllipsize();
}
// Convert to supported TruncateAt values
@@ -1024,7 +1263,7 @@ public void setContentScrimColor(@ColorInt int color) {
* @see #getContentScrim()
*/
public void setContentScrimResource(@DrawableRes int resId) {
- setContentScrim(ContextCompat.getDrawable(getContext(), resId));
+ setContentScrim(getContext().getDrawable(resId));
}
/**
@@ -1082,8 +1321,8 @@ protected void drawableStateChanged() {
if (d != null && d.isStateful()) {
changed |= d.setState(state);
}
- if (collapsingTextHelper != null) {
- changed |= collapsingTextHelper.setState(state);
+ if (collapsingTitleHelper != null) {
+ changed |= collapsingTitleHelper.setState(state);
}
if (changed) {
@@ -1130,7 +1369,7 @@ public void setStatusBarScrimColor(@ColorInt int color) {
* @see #getStatusBarScrim()
*/
public void setStatusBarScrimResource(@DrawableRes int resId) {
- setStatusBarScrim(ContextCompat.getDrawable(getContext(), resId));
+ setStatusBarScrim(getContext().getDrawable(resId));
}
/**
@@ -1152,7 +1391,18 @@ public Drawable getStatusBarScrim() {
* com.google.android.material.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance
*/
public void setCollapsedTitleTextAppearance(@StyleRes int resId) {
- collapsingTextHelper.setCollapsedTextAppearance(resId);
+ collapsingTitleHelper.setCollapsedTextAppearance(resId);
+ }
+
+ /**
+ * Sets the text color and size for the collapsed subtitle from the specified TextAppearance
+ * resource.
+ *
+ * @attr ref
+ * com.google.android.material.R.styleable#CollapsingToolbarLayout_collapsedSubtitleTextAppearance
+ */
+ public void setCollapsedSubtitleTextAppearance(@StyleRes int resId) {
+ collapsingSubtitleHelper.setCollapsedTextAppearance(resId);
}
/**
@@ -1170,26 +1420,46 @@ public void setCollapsedTitleTextColor(@ColorInt int color) {
* @param colors ColorStateList containing the new text colors
*/
public void setCollapsedTitleTextColor(@NonNull ColorStateList colors) {
- collapsingTextHelper.setCollapsedTextColor(colors);
+ collapsingTitleHelper.setCollapsedTextColor(colors);
}
/**
- * Sets the horizontal alignment of the collapsed title and the vertical gravity that will be used
- * when there is extra space in the collapsed bounds beyond what is required for the title itself.
+ * Sets the text color of the collapsed subtitle.
+ *
+ * @param color The new text color in ARGB format
+ */
+ public void setCollapsedSubtitleTextColor(@ColorInt int color) {
+ setCollapsedSubtitleTextColor(ColorStateList.valueOf(color));
+ }
+
+ /**
+ * Sets the text colors of the collapsed subtitle.
+ *
+ * @param colors ColorStateList containing the new text colors
+ */
+ public void setCollapsedSubtitleTextColor(@NonNull ColorStateList colors) {
+ collapsingSubtitleHelper.setCollapsedTextColor(colors);
+ }
+
+ /**
+ * Sets the horizontal alignment of the collapsed titles and the vertical gravity that will be
+ * used when there is extra space in the collapsed bounds beyond what is required for the title
+ * itself.
*
* @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity
*/
public void setCollapsedTitleGravity(int gravity) {
- collapsingTextHelper.setCollapsedTextGravity(gravity);
+ collapsingTitleHelper.setCollapsedTextGravity(gravity);
+ collapsingSubtitleHelper.setCollapsedTextGravity(gravity);
}
/**
- * Returns the horizontal and vertical alignment for title when collapsed.
+ * Returns the horizontal and vertical alignment for titles when collapsed.
*
* @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity
*/
public int getCollapsedTitleGravity() {
- return collapsingTextHelper.getCollapsedTextGravity();
+ return collapsingTitleHelper.getCollapsedTextGravity();
}
/**
@@ -1199,7 +1469,18 @@ public int getCollapsedTitleGravity() {
* com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance
*/
public void setExpandedTitleTextAppearance(@StyleRes int resId) {
- collapsingTextHelper.setExpandedTextAppearance(resId);
+ collapsingTitleHelper.setExpandedTextAppearance(resId);
+ }
+
+ /**
+ * Sets the text color and size for the expanded subtitle from the specified TextAppearance
+ * resource.
+ *
+ * @attr ref
+ * com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedSubtitleTextAppearance
+ */
+ public void setExpandedSubtitleTextAppearance(@StyleRes int resId) {
+ collapsingSubtitleHelper.setExpandedTextAppearance(resId);
}
/**
@@ -1217,26 +1498,45 @@ public void setExpandedTitleColor(@ColorInt int color) {
* @param colors ColorStateList containing the new text colors
*/
public void setExpandedTitleTextColor(@NonNull ColorStateList colors) {
- collapsingTextHelper.setExpandedTextColor(colors);
+ collapsingTitleHelper.setExpandedTextColor(colors);
+ }
+
+ /**
+ * Sets the text color of the expanded subtitle.
+ *
+ * @param color The new text color in ARGB format
+ */
+ public void setExpandedSubtitleColor(@ColorInt int color) {
+ setExpandedSubtitleTextColor(ColorStateList.valueOf(color));
}
/**
- * Sets the horizontal alignment of the expanded title and the vertical gravity that will be used
+ * Sets the text colors of the expanded subtitle.
+ *
+ * @param colors ColorStateList containing the new text colors
+ */
+ public void setExpandedSubtitleTextColor(@NonNull ColorStateList colors) {
+ collapsingSubtitleHelper.setExpandedTextColor(colors);
+ }
+
+ /**
+ * Sets the horizontal alignment of the expanded titles and the vertical gravity that will be used
* when there is extra space in the expanded bounds beyond what is required for the title itself.
*
* @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleGravity
*/
public void setExpandedTitleGravity(int gravity) {
- collapsingTextHelper.setExpandedTextGravity(gravity);
+ collapsingTitleHelper.setExpandedTextGravity(gravity);
+ collapsingSubtitleHelper.setExpandedTextGravity(gravity);
}
/**
- * Returns the horizontal and vertical alignment for title when expanded.
+ * Returns the horizontal and vertical alignment for titles when expanded.
*
* @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleGravity
*/
public int getExpandedTitleGravity() {
- return collapsingTextHelper.getExpandedTextGravity();
+ return collapsingTitleHelper.getExpandedTextGravity();
}
/**
@@ -1245,12 +1545,26 @@ public int getExpandedTitleGravity() {
* @param textSize The text size of the expanded title.
*/
public void setExpandedTitleTextSize(float textSize) {
- collapsingTextHelper.setExpandedTextSize(textSize);
+ collapsingTitleHelper.setExpandedTextSize(textSize);
+ }
+
+ /**
+ * Sets the text size of the expanded subtitle.
+ *
+ * @param textSize The text size of the expanded subtitle.
+ */
+ public void setExpandedSubtitleTextSize(float textSize) {
+ collapsingSubtitleHelper.setExpandedTextSize(textSize);
}
/** Returns the text size of the expanded title. */
public float getExpandedTitleTextSize() {
- return collapsingTextHelper.getExpandedTextSize();
+ return collapsingTitleHelper.getExpandedTextSize();
+ }
+
+ /** Returns the text size of the expanded subtitle. */
+ public float getExpandedSubtitleTextSize() {
+ return collapsingSubtitleHelper.getExpandedTextSize();
}
/**
@@ -1259,12 +1573,26 @@ public float getExpandedTitleTextSize() {
* @param textSize The text size of the collapsed title.
*/
public void setCollapsedTitleTextSize(float textSize) {
- collapsingTextHelper.setCollapsedTextSize(textSize);
+ collapsingTitleHelper.setCollapsedTextSize(textSize);
+ }
+
+ /**
+ * Sets the text size of the collapsed subtitle.
+ *
+ * @param textSize The text size of the collapsed subtitle.
+ */
+ public void setCollapsedSubtitleTextSize(float textSize) {
+ collapsingSubtitleHelper.setCollapsedTextSize(textSize);
}
/** Returns the text size of the collapsed title. */
public float getCollapsedTitleTextSize() {
- return collapsingTextHelper.getCollapsedTextSize();
+ return collapsingTitleHelper.getCollapsedTextSize();
+ }
+
+ /** Returns the text size of the collapsed subtitle. */
+ public float getCollapsedSubtitleTextSize() {
+ return collapsingSubtitleHelper.getCollapsedTextSize();
}
/**
@@ -1273,13 +1601,28 @@ public float getCollapsedTitleTextSize() {
* @param typeface typeface to use, or {@code null} to use the default.
*/
public void setCollapsedTitleTypeface(@Nullable Typeface typeface) {
- collapsingTextHelper.setCollapsedTypeface(typeface);
+ collapsingTitleHelper.setCollapsedTypeface(typeface);
+ }
+
+ /**
+ * Set the typeface to use for the collapsed subtitle.
+ *
+ * @param typeface typeface to use, or {@code null} to use the default.
+ */
+ public void setCollapsedSubtitleTypeface(@Nullable Typeface typeface) {
+ collapsingSubtitleHelper.setCollapsedTypeface(typeface);
}
/** Returns the typeface used for the collapsed title. */
@NonNull
public Typeface getCollapsedTitleTypeface() {
- return collapsingTextHelper.getCollapsedTypeface();
+ return collapsingTitleHelper.getCollapsedTypeface();
+ }
+
+ /** Returns the typeface used for the collapsed subtitle. */
+ @NonNull
+ public Typeface getCollapsedSubtitleTypeface() {
+ return collapsingSubtitleHelper.getCollapsedTypeface();
}
/**
@@ -1288,13 +1631,28 @@ public Typeface getCollapsedTitleTypeface() {
* @param typeface typeface to use, or {@code null} to use the default.
*/
public void setExpandedTitleTypeface(@Nullable Typeface typeface) {
- collapsingTextHelper.setExpandedTypeface(typeface);
+ collapsingTitleHelper.setExpandedTypeface(typeface);
+ }
+
+ /**
+ * Set the typeface to use for the expanded subtitle.
+ *
+ * @param typeface typeface to use, or {@code null} to use the default.
+ */
+ public void setExpandedSubtitleTypeface(@Nullable Typeface typeface) {
+ collapsingSubtitleHelper.setExpandedTypeface(typeface);
}
/** Returns the typeface used for the expanded title. */
@NonNull
public Typeface getExpandedTitleTypeface() {
- return collapsingTextHelper.getExpandedTypeface();
+ return collapsingTitleHelper.getExpandedTypeface();
+ }
+
+ /** Returns the typeface used for the expanded subtitle. */
+ @NonNull
+ public Typeface getExpandedSubtitleTypeface() {
+ return collapsingSubtitleHelper.getExpandedTypeface();
}
/**
@@ -1321,7 +1679,8 @@ public void setExpandedTitleMargin(int start, int top, int end, int bottom) {
/**
* @return the starting expanded title margin in pixels
* @see #setExpandedTitleMarginStart(int)
- * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart
+ * @attr ref
+ * com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart
*/
public int getExpandedTitleMarginStart() {
return expandedMarginStart;
@@ -1332,7 +1691,8 @@ public int getExpandedTitleMarginStart() {
*
* @param margin the starting title margin in pixels
* @see #getExpandedTitleMarginStart()
- * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart
+ * @attr ref
+ * com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart
*/
public void setExpandedTitleMarginStart(int margin) {
expandedMarginStart = margin;
@@ -1342,7 +1702,8 @@ public void setExpandedTitleMarginStart(int margin) {
/**
* @return the top expanded title margin in pixels
* @see #setExpandedTitleMarginTop(int)
- * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginTop
+ * @attr ref
+ * com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginTop
*/
public int getExpandedTitleMarginTop() {
return expandedMarginTop;
@@ -1353,7 +1714,8 @@ public int getExpandedTitleMarginTop() {
*
* @param margin the top title margin in pixels
* @see #getExpandedTitleMarginTop()
- * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginTop
+ * @attr ref
+ * com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginTop
*/
public void setExpandedTitleMarginTop(int margin) {
expandedMarginTop = margin;
@@ -1363,7 +1725,8 @@ public void setExpandedTitleMarginTop(int margin) {
/**
* @return the ending expanded title margin in pixels
* @see #setExpandedTitleMarginEnd(int)
- * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
+ * @attr ref
+ * com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
*/
public int getExpandedTitleMarginEnd() {
return expandedMarginEnd;
@@ -1374,7 +1737,8 @@ public int getExpandedTitleMarginEnd() {
*
* @param margin the ending title margin in pixels
* @see #getExpandedTitleMarginEnd()
- * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
+ * @attr ref
+ * com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
*/
public void setExpandedTitleMarginEnd(int margin) {
expandedMarginEnd = margin;
@@ -1384,7 +1748,8 @@ public void setExpandedTitleMarginEnd(int margin) {
/**
* @return the bottom expanded title margin in pixels
* @see #setExpandedTitleMarginBottom(int)
- * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
+ * @attr ref
+ * com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
*/
public int getExpandedTitleMarginBottom() {
return expandedMarginBottom;
@@ -1395,7 +1760,8 @@ public int getExpandedTitleMarginBottom() {
*
* @param margin the bottom title margin in pixels
* @see #getExpandedTitleMarginBottom()
- * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
+ * @attr ref
+ * com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
*/
public void setExpandedTitleMarginBottom(int margin) {
expandedMarginBottom = margin;
@@ -1403,30 +1769,44 @@ public void setExpandedTitleMarginBottom(int margin) {
}
/**
- * Sets the maximum number of lines to display in the expanded state.
- * Experimental Feature.
+ * Returns the spacing between the expanded title and subtitle in pixels
+ *
+ * @see #setExpandedTitleSpacing(int)
+ * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleSpacing
*/
- @RestrictTo(LIBRARY_GROUP)
- public void setMaxLines(int maxLines) {
- collapsingTextHelper.setExpandedMaxLines(maxLines);
+ public int getExpandedTitleSpacing() {
+ return expandedTitleSpacing;
}
/**
- * Gets the maximum number of lines to display in the expanded state.
- * Experimental Feature.
+ * Sets the spacing between the expanded title and subtitle.
+ *
+ * @param titleSpacing the spacing between the expanded title and subtitle in pixels
+ * @see #getExpandedTitleSpacing()
+ * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleSpacing
*/
+ public void setExpandedTitleSpacing(int titleSpacing) {
+ expandedTitleSpacing = titleSpacing;
+ requestLayout();
+ }
+
+ /** Sets the maximum number of lines to display in the expanded state. Experimental Feature. */
+ @RestrictTo(LIBRARY_GROUP)
+ public void setMaxLines(int maxLines) {
+ collapsingTitleHelper.setExpandedMaxLines(maxLines);
+ collapsingSubtitleHelper.setExpandedMaxLines(maxLines);
+ }
+
+ /** Gets the maximum number of lines to display in the expanded state. Experimental Feature. */
@RestrictTo(LIBRARY_GROUP)
public int getMaxLines() {
- return collapsingTextHelper.getExpandedMaxLines();
+ return collapsingTitleHelper.getExpandedMaxLines();
}
- /**
- * Gets the current number of lines of the title text.
- * Experimental Feature.
- */
+ /** Gets the current number of lines of the title text. Experimental Feature. */
@RestrictTo(LIBRARY_GROUP)
public int getLineCount() {
- return collapsingTextHelper.getLineCount();
+ return collapsingTitleHelper.getLineCount();
}
/**
@@ -1436,14 +1816,14 @@ public int getLineCount() {
@RestrictTo(LIBRARY_GROUP)
@RequiresApi(VERSION_CODES.M)
public void setLineSpacingAdd(float spacingAdd) {
- collapsingTextHelper.setLineSpacingAdd(spacingAdd);
+ collapsingTitleHelper.setLineSpacingAdd(spacingAdd);
}
/** Gets the line spacing addition of the title text, or -1 if not set. Experimental Feature. */
@RestrictTo(LIBRARY_GROUP)
@RequiresApi(VERSION_CODES.M)
public float getLineSpacingAdd() {
- return collapsingTextHelper.getLineSpacingAdd();
+ return collapsingTitleHelper.getLineSpacingAdd();
}
/**
@@ -1453,14 +1833,14 @@ public float getLineSpacingAdd() {
@RestrictTo(LIBRARY_GROUP)
@RequiresApi(VERSION_CODES.M)
public void setLineSpacingMultiplier(@FloatRange(from = 0.0) float spacingMultiplier) {
- collapsingTextHelper.setLineSpacingMultiplier(spacingMultiplier);
+ collapsingTitleHelper.setLineSpacingMultiplier(spacingMultiplier);
}
/** Gets the line spacing multiplier of the title text, or -1 if not set. Experimental Feature. */
@RestrictTo(LIBRARY_GROUP)
@RequiresApi(VERSION_CODES.M)
public float getLineSpacingMultiplier() {
- return collapsingTextHelper.getLineSpacingMultiplier();
+ return collapsingTitleHelper.getLineSpacingMultiplier();
}
/**
@@ -1470,14 +1850,14 @@ public float getLineSpacingMultiplier() {
@RestrictTo(LIBRARY_GROUP)
@RequiresApi(VERSION_CODES.M)
public void setHyphenationFrequency(int hyphenationFrequency) {
- collapsingTextHelper.setHyphenationFrequency(hyphenationFrequency);
+ collapsingTitleHelper.setHyphenationFrequency(hyphenationFrequency);
}
/** Gets the hyphenation frequency of the title text, or -1 if not set. Experimental Feature. */
@RestrictTo(LIBRARY_GROUP)
@RequiresApi(VERSION_CODES.M)
public int getHyphenationFrequency() {
- return collapsingTextHelper.getHyphenationFrequency();
+ return collapsingTitleHelper.getHyphenationFrequency();
}
/**
@@ -1495,7 +1875,7 @@ public int getHyphenationFrequency() {
@RequiresApi(VERSION_CODES.M)
public void setStaticLayoutBuilderConfigurer(
@Nullable StaticLayoutBuilderConfigurer staticLayoutBuilderConfigurer) {
- collapsingTextHelper.setStaticLayoutBuilderConfigurer(staticLayoutBuilderConfigurer);
+ collapsingTitleHelper.setStaticLayoutBuilderConfigurer(staticLayoutBuilderConfigurer);
}
/**
@@ -1504,7 +1884,7 @@ public void setStaticLayoutBuilderConfigurer(
*/
@RestrictTo(LIBRARY_GROUP)
public void setRtlTextDirectionHeuristicsEnabled(boolean rtlTextDirectionHeuristicsEnabled) {
- collapsingTextHelper.setRtlTextDirectionHeuristicsEnabled(rtlTextDirectionHeuristicsEnabled);
+ collapsingTitleHelper.setRtlTextDirectionHeuristicsEnabled(rtlTextDirectionHeuristicsEnabled);
}
/**
@@ -1513,12 +1893,12 @@ public void setRtlTextDirectionHeuristicsEnabled(boolean rtlTextDirectionHeurist
*/
@RestrictTo(LIBRARY_GROUP)
public boolean isRtlTextDirectionHeuristicsEnabled() {
- return collapsingTextHelper.isRtlTextDirectionHeuristicsEnabled();
+ return collapsingTitleHelper.isRtlTextDirectionHeuristicsEnabled();
}
/**
- * Sets whether the top system window inset should be respected regardless of what the
- * {@code layout_height} of the {@code CollapsingToolbarLayout} is set to. Experimental Feature.
+ * Sets whether the top system window inset should be respected regardless of what the {@code
+ * layout_height} of the {@code CollapsingToolbarLayout} is set to. Experimental Feature.
*/
@RestrictTo(LIBRARY_GROUP)
public void setForceApplySystemWindowInsetTop(boolean forceApplySystemWindowInsetTop) {
@@ -1526,8 +1906,8 @@ public void setForceApplySystemWindowInsetTop(boolean forceApplySystemWindowInse
}
/**
- * Gets whether the top system window inset should be respected regardless of what the
- * {@code layout_height} of the {@code CollapsingToolbarLayout} is set to. Experimental Feature.
+ * Gets whether the top system window inset should be respected regardless of what the {@code
+ * layout_height} of the {@code CollapsingToolbarLayout} is set to. Experimental Feature.
*/
@RestrictTo(LIBRARY_GROUP)
public boolean isForceApplySystemWindowInsetTop() {
@@ -1580,7 +1960,11 @@ public void setScrimVisibleHeightTrigger(@IntRange(from = 0) final int height) {
public int getScrimVisibleHeightTrigger() {
if (scrimVisibleHeightTrigger >= 0) {
// If we have one explicitly set, return it
- return scrimVisibleHeightTrigger + topInsetApplied + extraMultilineHeight;
+ return scrimVisibleHeightTrigger
+ + topInsetApplied
+ + extraMultilineTitleHeight
+ + extraMultilineSubtitleHeight
+ + extraHeightForTitles;
}
// Otherwise we'll use the default computed value
@@ -1606,7 +1990,7 @@ public int getScrimVisibleHeightTrigger() {
* com.google.android.material.R.styleable#CollapsingToolbarLayout_titlePositionInterpolator
*/
public void setTitlePositionInterpolator(@Nullable TimeInterpolator interpolator) {
- collapsingTextHelper.setPositionInterpolator(interpolator);
+ collapsingTitleHelper.setPositionInterpolator(interpolator);
}
/**
@@ -1615,14 +1999,15 @@ public void setTitlePositionInterpolator(@Nullable TimeInterpolator interpolator
*/
@Nullable
public TimeInterpolator getTitlePositionInterpolator() {
- return collapsingTextHelper.getPositionInterpolator();
+ return collapsingTitleHelper.getPositionInterpolator();
}
/**
* Set the duration used for scrim visibility animations.
*
* @param duration the duration to use in milliseconds
- * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_scrimAnimationDuration
+ * @attr ref
+ * com.google.android.material.R.styleable#CollapsingToolbarLayout_scrimAnimationDuration
*/
public void setScrimAnimationDuration(@IntRange(from = 0) final long duration) {
scrimAnimationDuration = duration;
@@ -1823,10 +2208,16 @@ public void onOffsetChanged(AppBarLayout layout, int verticalOffset) {
int height = getHeight();
final int expandRange = height - getMinimumHeight() - insetTop;
final int scrimRange = height - getScrimVisibleHeightTrigger();
- collapsingTextHelper.setFadeModeStartFraction(
+ final int currentOffsetY = currentOffset + expandRange;
+ final float expansionFraction = Math.abs(verticalOffset) / (float) expandRange;
+ collapsingTitleHelper.setFadeModeStartFraction(
+ Math.min(1, (float) scrimRange / (float) expandRange));
+ collapsingTitleHelper.setCurrentOffsetY(currentOffsetY);
+ collapsingTitleHelper.setExpansionFraction(expansionFraction);
+ collapsingSubtitleHelper.setFadeModeStartFraction(
Math.min(1, (float) scrimRange / (float) expandRange));
- collapsingTextHelper.setCurrentOffsetY(currentOffset + expandRange);
- collapsingTextHelper.setExpansionFraction(Math.abs(verticalOffset) / (float) expandRange);
+ collapsingSubtitleHelper.setCurrentOffsetY(currentOffsetY);
+ collapsingSubtitleHelper.setExpansionFraction(expansionFraction);
}
}
diff --git a/lib/java/com/google/android/material/appbar/MaterialToolbar.java b/lib/java/com/google/android/material/appbar/MaterialToolbar.java
index 9971af44a6d..7dc96d6d62b 100644
--- a/lib/java/com/google/android/material/appbar/MaterialToolbar.java
+++ b/lib/java/com/google/android/material/appbar/MaterialToolbar.java
@@ -26,11 +26,9 @@
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
-import androidx.appcompat.view.menu.MenuBuilder;
import androidx.appcompat.widget.Toolbar;
import android.util.AttributeSet;
import android.util.Pair;
-import android.view.Menu;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
@@ -38,7 +36,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.DrawableCompat;
-import androidx.core.view.ViewCompat;
import com.google.android.material.drawable.DrawableUtils;
import com.google.android.material.internal.ThemeEnforcement;
import com.google.android.material.internal.ToolbarUtils;
@@ -93,7 +90,7 @@ public MaterialToolbar(@NonNull Context context) {
}
public MaterialToolbar(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, R.attr.toolbarStyle);
+ this(context, attrs, androidx.appcompat.R.attr.toolbarStyle);
}
public MaterialToolbar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
@@ -126,19 +123,6 @@ public MaterialToolbar(@NonNull Context context, @Nullable AttributeSet attrs, i
initBackground(context);
}
- @Override
- public void inflateMenu(int i) {
- // Pause dispatching item changes during inflation to improve performance.
- Menu menu = getMenu();
- if (menu instanceof MenuBuilder) {
- ((MenuBuilder) menu).stopDispatchingItemsChanged();
- }
- super.inflateMenu(i);
- if (menu instanceof MenuBuilder) {
- ((MenuBuilder) menu).startDispatchingItemsChanged();
- }
- }
-
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
@@ -308,7 +292,7 @@ public void clearNavigationIconTint() {
Drawable navigationIcon = getNavigationIcon();
if (navigationIcon != null) {
Drawable wrappedNavigationIcon = DrawableCompat.wrap(navigationIcon.mutate());
- DrawableCompat.setTintList(wrappedNavigationIcon, null);
+ wrappedNavigationIcon.setTintList(null);
setNavigationIcon(navigationIcon);
}
}
@@ -383,7 +367,7 @@ private void initBackground(Context context) {
MaterialShapeDrawable materialShapeDrawable = new MaterialShapeDrawable();
materialShapeDrawable.setFillColor(backgroundColorStateList);
materialShapeDrawable.initializeElevationOverlay(context);
- materialShapeDrawable.setElevation(ViewCompat.getElevation(this));
+ materialShapeDrawable.setElevation(getElevation());
setBackground(materialShapeDrawable);
}
}
@@ -392,7 +376,7 @@ private void initBackground(Context context) {
private Drawable maybeTintNavigationIcon(@Nullable Drawable navigationIcon) {
if (navigationIcon != null && navigationIconTint != null) {
Drawable wrappedNavigationIcon = DrawableCompat.wrap(navigationIcon.mutate());
- DrawableCompat.setTint(wrappedNavigationIcon, navigationIconTint);
+ wrappedNavigationIcon.setTint(navigationIconTint);
return wrappedNavigationIcon;
} else {
return navigationIcon;
diff --git a/lib/java/com/google/android/material/appbar/res-public/values/public.xml b/lib/java/com/google/android/material/appbar/res-public/values/public.xml
index b350134b0db..7a232eb3d3e 100644
--- a/lib/java/com/google/android/material/appbar/res-public/values/public.xml
+++ b/lib/java/com/google/android/material/appbar/res-public/values/public.xml
@@ -30,10 +30,15 @@
+
+
+
+
+
@@ -74,6 +79,7 @@
+
@@ -84,6 +90,8 @@
+
+
diff --git a/lib/java/com/google/android/material/appbar/res/animator/m3_appbar_state_list_animator.xml b/lib/java/com/google/android/material/appbar/res/animator/m3_appbar_state_list_animator.xml
index b1805e1e40d..fb75622067f 100644
--- a/lib/java/com/google/android/material/appbar/res/animator/m3_appbar_state_list_animator.xml
+++ b/lib/java/com/google/android/material/appbar/res/animator/m3_appbar_state_list_animator.xml
@@ -23,7 +23,7 @@
@@ -31,7 +31,7 @@
@@ -39,7 +39,7 @@
diff --git a/lib/java/com/google/android/material/appbar/res/values/attrs.xml b/lib/java/com/google/android/material/appbar/res/values/attrs.xml
index 87dc89a9509..1090c1a2c37 100644
--- a/lib/java/com/google/android/material/appbar/res/values/attrs.xml
+++ b/lib/java/com/google/android/material/appbar/res/values/attrs.xml
@@ -134,27 +134,35 @@
-
-
-
-
-
+
+
+
+
+
+
@@ -168,6 +176,12 @@
+
+
+
+
@@ -237,6 +251,8 @@
+
+
@@ -244,8 +260,11 @@
-
+
+
+
+
@@ -256,6 +275,16 @@
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/appbar/res/values/dimens.xml b/lib/java/com/google/android/material/appbar/res/values/dimens.xml
index 4184e1d68e2..765fc32b69d 100644
--- a/lib/java/com/google/android/material/appbar/res/values/dimens.xml
+++ b/lib/java/com/google/android/material/appbar/res/values/dimens.xml
@@ -20,9 +20,11 @@
56dp
- @dimen/m3_comp_top_app_bar_small_container_height
- @dimen/m3_comp_top_app_bar_medium_container_height
- @dimen/m3_comp_top_app_bar_large_container_height
+ @dimen/m3_comp_app_bar_small_container_height
+ @dimen/m3_comp_app_bar_medium_container_height
+ 136dp
+ @dimen/m3_comp_app_bar_large_container_height
+ 152dp
16dp
16dp
diff --git a/lib/java/com/google/android/material/appbar/res/values/styles.xml b/lib/java/com/google/android/material/appbar/res/values/styles.xml
index 542011cc589..1e7041b9452 100644
--- a/lib/java/com/google/android/material/appbar/res/values/styles.xml
+++ b/lib/java/com/google/android/material/appbar/res/values/styles.xml
@@ -16,14 +16,192 @@
-->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -58,7 +236,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/lib/java/com/google/android/material/appbar/res/values/tokens.xml b/lib/java/com/google/android/material/appbar/res/values/tokens.xml
index 280cafb3d54..d271b8580f1 100644
--- a/lib/java/com/google/android/material/appbar/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/appbar/res/values/tokens.xml
@@ -15,38 +15,43 @@
~ limitations under the License.
-->
-
+
-
-
- ?attr/colorSurface
- ?attr/colorSurfaceContainer
- 64dp
- @dimen/m3_sys_elevation_level0
-
- ?attr/colorOnSurface
-
- ?attr/colorOnSurfaceVariant
-
- ?attr/textAppearanceTitleLarge
- ?attr/colorOnSurface
-
- @dimen/m3_sys_elevation_level2
-
-
-
- 112dp
-
- ?attr/textAppearanceHeadlineSmall
- ?attr/colorOnSurface
-
-
-
- 152dp
-
- ?attr/textAppearanceHeadlineMedium
- ?attr/colorOnSurface
+
+
+ ?attr/colorSurface
+ ?attr/colorSurfaceContainer
+ @dimen/m3_sys_elevation_level0
+ @dimen/m3_sys_elevation_level2
+ ?attr/colorOnSurface
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurface
+ ?attr/colorOnSurfaceVariant
+
+
+ 64dp
+ ?attr/textAppearanceTitleLarge
+ ?attr/textAppearanceLabelMedium
+
+
+ 112dp
+ ?attr/textAppearanceHeadlineSmall
+
+
+ 152dp
+ ?attr/textAppearanceHeadlineMedium
+
+
+ 112dp
+ ?attr/textAppearanceHeadlineMedium
+ ?attr/textAppearanceLabelLarge
+
+
+ 120dp
+ ?attr/textAppearanceDisplaySmall
+ ?attr/textAppearanceTitleMedium
diff --git a/lib/java/com/google/android/material/badge/res/values-af/strings.xml b/lib/java/com/google/android/material/badge/res/values-af/strings.xml
index b3d74ce1051..d306357005a 100644
--- a/lib/java/com/google/android/material/badge/res/values-af/strings.xml
+++ b/lib/java/com/google/android/material/badge/res/values-af/strings.xml
@@ -1,6 +1,6 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
diff --git a/lib/java/com/google/android/material/bottomnavigation/res/values/tokens.xml b/lib/java/com/google/android/material/bottomnavigation/res/values/tokens.xml
index 02d8af864c0..a97967cf1b5 100644
--- a/lib/java/com/google/android/material/bottomnavigation/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/bottomnavigation/res/values/tokens.xml
@@ -15,50 +15,45 @@
~ limitations under the License.
-->
-
+
-
-
- ?attr/colorSurfaceContainer
-
- 80dp
- @dimen/m3_sys_elevation_level2
-
- ?attr/textAppearanceLabelMedium
- ?attr/colorOnSurface
- ?attr/colorOnSurfaceVariant
- ?attr/colorOnSurface
- ?attr/colorOnSurface
- ?attr/colorOnSurface
- ?attr/colorOnSurface
- ?attr/colorOnSurface
- ?attr/colorOnSurface
-
- 24dp
- ?attr/colorOnSecondaryContainer
- ?attr/colorOnSurfaceVariant
- ?attr/colorOnSecondaryContainer
- ?attr/colorOnSurface
- ?attr/colorOnSecondaryContainer
- ?attr/colorOnSurface
- ?attr/colorOnSecondaryContainer
- ?attr/colorOnSurface
-
- ?attr/colorSecondaryContainer
- 32dp
- 64dp
-
-
- ?attr/colorOnSurface
- ?attr/colorOnSurface
- @dimen/m3_sys_state_hover_state_layer_opacity
- ?attr/colorOnSurface
- ?attr/colorOnSurface
- @dimen/m3_sys_state_focus_state_layer_opacity
- ?attr/colorOnSurface
- ?attr/colorOnSurface
- @dimen/m3_sys_state_pressed_state_layer_opacity
+
+
+ @dimen/m3_sys_elevation_level2
+ ?attr/colorSurfaceContainer
+ ?attr/colorSecondaryContainer
+ ?attr/colorSecondary
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSecondaryContainer
+ ?attr/colorOnSurfaceVariant
+
+ ?attr/colorOnSecondaryContainer
+ ?attr/colorOnSecondaryContainer
+
+ ?attr/colorOnSecondaryContainer
+ ?attr/colorOnSecondaryContainer
+
+ ?attr/colorOnSecondaryContainer
+ ?attr/colorOnSecondaryContainer
+
+
+ 4dp
+ 24dp
+
+ 64dp
+
+
+
+ ?attr/textAppearanceLabelMedium
+ 40dp
+
+
+ ?attr/textAppearanceLabelMedium
+ 32dp
+ 56dp
+ 6dp
diff --git a/lib/java/com/google/android/material/bottomsheet/BottomSheetBehavior.java b/lib/java/com/google/android/material/bottomsheet/BottomSheetBehavior.java
index 8327c6f8887..ddf66bc83e8 100644
--- a/lib/java/com/google/android/material/bottomsheet/BottomSheetBehavior.java
+++ b/lib/java/com/google/android/material/bottomsheet/BottomSheetBehavior.java
@@ -33,6 +33,7 @@
import android.os.Build.VERSION_CODES;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseIntArray;
@@ -328,6 +329,7 @@ void onLayout(@NonNull View bottomSheet) {}
@Nullable WeakReference viewRef;
@Nullable WeakReference accessibilityDelegateViewRef;
+ @Nullable WeakReference dragHandleViewRef;
@Nullable WeakReference nestedScrollingChildRef;
@@ -559,8 +561,7 @@ public boolean onLayoutChild(
if (materialShapeDrawable != null) {
child.setBackground(materialShapeDrawable);
// Use elevation attr if set on bottomsheet; otherwise, use elevation of child view.
- materialShapeDrawable.setElevation(
- elevation == -1 ? ViewCompat.getElevation(child) : elevation);
+ materialShapeDrawable.setElevation(elevation == -1 ? child.getElevation() : elevation);
} else if (backgroundTint != null) {
ViewCompat.setBackgroundTintList(child, backgroundTint);
}
@@ -649,10 +650,11 @@ public boolean onInterceptTouchEvent(
// Only intercept nested scrolling events here if the view not being moved by the
// ViewDragHelper.
if (state != STATE_SETTLING) {
- View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;
- if (scroll != null && parent.isPointInChildBounds(scroll, initialX, initialY)) {
+ if (isTouchingScrollingChild(parent, initialX, initialY)) {
activePointerId = event.getPointerId(event.getActionIndex());
- touchingScrollingChild = true;
+ if (!isTouchingDragHandle(parent, initialX, initialY)) {
+ touchingScrollingChild = true;
+ }
}
}
ignoreEvents =
@@ -1217,7 +1219,7 @@ public boolean isDraggableOnNestedScroll() {
return draggableOnNestedScroll;
}
- /*
+ /**
* Sets the velocity threshold considered significant enough to trigger a slide
* to the next stable state.
*
@@ -1229,7 +1231,7 @@ public void setSignificantVelocityThreshold(int significantVelocityThreshold) {
this.significantVelocityThreshold = significantVelocityThreshold;
}
- /*
+ /**
* Returns the significant velocity threshold.
*
* @see #setSignificantVelocityThreshold(int)
@@ -1532,6 +1534,20 @@ private float calculateCornerInterpolation(
return 0;
}
+ private boolean isTouchingScrollingChild(
+ @NonNull CoordinatorLayout parent, int xCoordinate, int yCoordinate) {
+ View scrollingChild = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;
+ return scrollingChild != null
+ && parent.isPointInChildBounds(scrollingChild, xCoordinate, yCoordinate);
+ }
+
+ private boolean isTouchingDragHandle(
+ @NonNull CoordinatorLayout parent, int xCoordinate, int yCoordinate) {
+ View dragHandleView = dragHandleViewRef != null ? dragHandleViewRef.get() : null;
+ return dragHandleView != null
+ && parent.isPointInChildBounds(dragHandleView, xCoordinate, yCoordinate);
+ }
+
private boolean isAtTopOfScreen() {
if (viewRef == null || viewRef.get() == null) {
return false;
@@ -1692,7 +1708,7 @@ View findScrollingChild(View view) {
if (view.getVisibility() != View.VISIBLE) {
return null;
}
- if (ViewCompat.isNestedScrollingEnabled(view)) {
+ if (view.isNestedScrollingEnabled()) {
return view;
}
if (view instanceof ViewGroup) {
@@ -1900,7 +1916,7 @@ public boolean tryCaptureView(@NonNull View child, int pointerId) {
return false;
}
}
- viewCapturedMillis = System.currentTimeMillis();
+ viewCapturedMillis = SystemClock.uptimeMillis();
return viewRef != null && viewRef.get() == child;
}
@@ -1930,7 +1946,7 @@ public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel)
targetState = STATE_EXPANDED;
} else {
int currentTop = releasedChild.getTop();
- long dragDurationMillis = System.currentTimeMillis() - viewCapturedMillis;
+ long dragDurationMillis = SystemClock.uptimeMillis() - viewCapturedMillis;
if (shouldSkipHalfExpandedStateWhenDragging()) {
float yPositionPercentage = currentTop * 100f / parentHeight;
@@ -2020,10 +2036,7 @@ public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel)
@Override
public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
- return MathUtils.clamp(
- top,
- getExpandedOffset(),
- getViewVerticalDragRange(child));
+ return MathUtils.clamp(top, getExpandedOffset(), getViewVerticalDragRange(child));
}
@Override
@@ -2346,6 +2359,10 @@ private void updateImportantForAccessibility(boolean expanded) {
}
}
+ void setDragHandleView(@Nullable BottomSheetDragHandleView dragHandleView) {
+ dragHandleViewRef = dragHandleView != null ? new WeakReference<>(dragHandleView) : null;
+ }
+
void setAccessibilityDelegateView(@Nullable View accessibilityDelegateView) {
if (accessibilityDelegateView == null && accessibilityDelegateViewRef != null) {
clearAccessibilityAction(
diff --git a/lib/java/com/google/android/material/bottomsheet/BottomSheetDialog.java b/lib/java/com/google/android/material/bottomsheet/BottomSheetDialog.java
index febebc1d2e7..ed31f640116 100644
--- a/lib/java/com/google/android/material/bottomsheet/BottomSheetDialog.java
+++ b/lib/java/com/google/android/material/bottomsheet/BottomSheetDialog.java
@@ -215,8 +215,8 @@ public void onDetachedFromWindow() {
* or calling `dismiss()` from a `BottomSheetDialogFragment`, tapping outside a dialog, etc...
*
* The default animation to dismiss this dialog is a fade-out transition through a
- * windowAnimation. Call {@link #setDismissWithAnimation(true)} if you want to utilize the
- * BottomSheet animation instead.
+ * windowAnimation. Call {@link #setDismissWithAnimation(boolean)} with `true`
+ * if you want to utilize the BottomSheet animation instead.
*
*
If this function is called from a swipe down interaction, or dismissWithAnimation is false,
* then keep the default behavior.
@@ -306,7 +306,7 @@ private View wrapInBottomSheet(
if (edgeToEdgeEnabled) {
ViewCompat.setOnApplyWindowInsetsListener(
- bottomSheet,
+ container,
new OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat insets) {
@@ -453,7 +453,7 @@ private EdgeToEdgeCallback(
if (msd != null) {
backgroundTint = msd.getFillColor();
} else {
- backgroundTint = ViewCompat.getBackgroundTintList(bottomSheet);
+ backgroundTint = bottomSheet.getBackgroundTintList();
}
if (backgroundTint != null) {
diff --git a/lib/java/com/google/android/material/bottomsheet/BottomSheetDragHandleView.java b/lib/java/com/google/android/material/bottomsheet/BottomSheetDragHandleView.java
index a33f23ed16c..a6169a4c62b 100644
--- a/lib/java/com/google/android/material/bottomsheet/BottomSheetDragHandleView.java
+++ b/lib/java/com/google/android/material/bottomsheet/BottomSheetDragHandleView.java
@@ -21,9 +21,16 @@
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
import static com.google.android.material.theme.overlay.MaterialThemeOverlay.wrap;
+import android.annotation.SuppressLint;
import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
import androidx.appcompat.widget.AppCompatImageView;
import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewParent;
@@ -35,7 +42,6 @@
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.ViewCompat;
-import androidx.core.view.accessibility.AccessibilityEventCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback;
@@ -47,24 +53,32 @@
* clickable. Clicking the drag handle will toggle the bottom sheet between its collapsed and
* expanded states.
*/
-public class BottomSheetDragHandleView extends AppCompatImageView
- implements AccessibilityStateChangeListener {
+public class BottomSheetDragHandleView extends AppCompatImageView implements
+ AccessibilityStateChangeListener {
private static final int DEF_STYLE_RES = R.style.Widget_Material3_BottomSheet_DragHandle;
@Nullable private final AccessibilityManager accessibilityManager;
@Nullable private BottomSheetBehavior> bottomSheetBehavior;
- private boolean accessibilityServiceEnabled;
- private boolean interactable;
+ private final GestureDetector gestureDetector;
+
private boolean clickToExpand;
+ /**
+ * Track whether clients have set their own touch or click listeners on the drag handle.
+ *
+ * Setting a custom touch or click listener will override the default behavior of cycling through
+ * bottom sheet states when tapped and dismissing the sheet when double tapped. Clients can
+ * restore this behavior by setting their touch and click listeners back to null.
+ */
+ private boolean hasTouchListener = false;
+ private boolean hasClickListener = false;
+
private final String clickToExpandActionLabel =
getResources().getString(R.string.bottomsheet_action_expand);
private final String clickToCollapseActionLabel =
getResources().getString(R.string.bottomsheet_action_collapse);
- private final String clickFeedback =
- getResources().getString(R.string.bottomsheet_drag_handle_clicked);
private final BottomSheetCallback bottomSheetCallback =
new BottomSheetCallback() {
@@ -78,6 +92,34 @@ public void onStateChanged(
public void onSlide(@NonNull View bottomSheet, float slideOffset) {}
};
+ /**
+ * A gesture listener that handles both single and double taps on the drag handle.
+ *
+ * Single taps cycle through the available states of the bottom sheet. A double tap hides
+ * the sheet.
+ */
+ private final OnGestureListener gestureListener = new SimpleOnGestureListener() {
+
+ @Override
+ public boolean onDown(@NonNull MotionEvent e) {
+ return isClickable();
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(@NonNull MotionEvent e) {
+ return expandOrCollapseBottomSheetIfPossible();
+ }
+
+ @Override
+ public boolean onDoubleTap(@NonNull MotionEvent e) {
+ if (bottomSheetBehavior != null && bottomSheetBehavior.isHideable()) {
+ bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
+ return true;
+ }
+ return super.onDoubleTap(e);
+ }
+ };
+
public BottomSheetDragHandleView(@NonNull Context context) {
this(context, /* attrs= */ null);
}
@@ -86,6 +128,7 @@ public BottomSheetDragHandleView(@NonNull Context context, @Nullable AttributeSe
this(context, attrs, R.attr.bottomSheetDragHandleStyle);
}
+ @SuppressLint("ClickableViewAccessibility") // Will be handled by accessibility delegate
public BottomSheetDragHandleView(
@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(wrap(context, attrs, defStyleAttr, DEF_STYLE_RES), attrs, defStyleAttr);
@@ -93,11 +136,12 @@ public BottomSheetDragHandleView(
// Override the provided context with the wrapped one to prevent it from being used.
context = getContext();
+ gestureDetector =
+ new GestureDetector(context, gestureListener, new Handler(Looper.getMainLooper()));
+
accessibilityManager =
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
- updateInteractableState();
-
ViewCompat.setAccessibilityDelegate(
this,
new AccessibilityDelegateCompat() {
@@ -130,24 +174,49 @@ protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
+ @SuppressLint("ClickableViewAccessibility") // Will be handled by accessibility delegate
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (hasClickListener || hasTouchListener) {
+ // If clients have set their own click or touch listeners, do nothing.
+ return super.onTouchEvent(event);
+ }
+
+ return gestureDetector.onTouchEvent(event);
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public void setOnTouchListener(OnTouchListener l) {
+ hasTouchListener = l != null;
+ super.setOnTouchListener(l);
+ }
+
+ @Override
+ public void setOnClickListener(@Nullable OnClickListener l) {
+ hasClickListener = l != null;
+ super.setOnClickListener(l);
+ }
+
@Override
public void onAccessibilityStateChanged(boolean enabled) {
- accessibilityServiceEnabled = enabled;
- updateInteractableState();
+ // Do nothing.
}
private void setBottomSheetBehavior(@Nullable BottomSheetBehavior> behavior) {
if (bottomSheetBehavior != null) {
bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallback);
bottomSheetBehavior.setAccessibilityDelegateView(null);
+ bottomSheetBehavior.setDragHandleView(null);
}
bottomSheetBehavior = behavior;
if (bottomSheetBehavior != null) {
bottomSheetBehavior.setAccessibilityDelegateView(this);
+ bottomSheetBehavior.setDragHandleView(this);
onBottomSheetStateChanged(bottomSheetBehavior.getState());
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback);
}
- updateInteractableState();
+ setClickable(hasAttachedBehavior());
}
private void onBottomSheetStateChanged(@BottomSheetBehavior.State int state) {
@@ -163,12 +232,8 @@ private void onBottomSheetStateChanged(@BottomSheetBehavior.State int state) {
(v, args) -> expandOrCollapseBottomSheetIfPossible());
}
- private void updateInteractableState() {
- interactable = accessibilityServiceEnabled && bottomSheetBehavior != null;
- setImportantForAccessibility(bottomSheetBehavior != null
- ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
- : View.IMPORTANT_FOR_ACCESSIBILITY_NO);
- setClickable(interactable);
+ private boolean hasAttachedBehavior() {
+ return bottomSheetBehavior != null;
}
/**
@@ -182,10 +247,9 @@ private void updateInteractableState() {
* the previous state was EXPANDED) or EXPANDED (when the previous state was COLLAPSED.)
*/
private boolean expandOrCollapseBottomSheetIfPossible() {
- if (!interactable) {
+ if (!hasAttachedBehavior()) {
return false;
}
- announceAccessibilityEvent(clickFeedback);
boolean canHalfExpand =
!bottomSheetBehavior.isFitToContents()
&& !bottomSheetBehavior.shouldSkipHalfExpandedStateWhenDragging();
@@ -209,16 +273,6 @@ private boolean expandOrCollapseBottomSheetIfPossible() {
return true;
}
- private void announceAccessibilityEvent(String announcement) {
- if (accessibilityManager == null) {
- return;
- }
- AccessibilityEvent announce =
- AccessibilityEvent.obtain(AccessibilityEventCompat.TYPE_ANNOUNCEMENT);
- announce.getText().add(announcement);
- accessibilityManager.sendAccessibilityEvent(announce);
- }
-
/**
* Finds the first ancestor associated with a {@link BottomSheetBehavior}. If none is found,
* returns {@code null}.
diff --git a/lib/java/com/google/android/material/bottomsheet/res/values-af/strings.xml b/lib/java/com/google/android/material/bottomsheet/res/values-af/strings.xml
index 46c18701209..0a7d199faab 100644
--- a/lib/java/com/google/android/material/bottomsheet/res/values-af/strings.xml
+++ b/lib/java/com/google/android/material/bottomsheet/res/values-af/strings.xml
@@ -1,6 +1,6 @@
-
-
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
-
+
@@ -279,9 +717,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/button/res/values/tokens.xml b/lib/java/com/google/android/material/button/res/values/tokens.xml
index a7e105381df..b736fdb013c 100644
--- a/lib/java/com/google/android/material/button/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/button/res/values/tokens.xml
@@ -15,36 +15,106 @@
~ limitations under the License.
-->
-
+
+
+ 1dp
+ ?attr/textAppearanceLabelLarge
+ 20dp
+
+ ?attr/shapeAppearanceCornerMedium
+ 12dp
+ 8dp
+ 12dp
+ ?attr/shapeAppearanceCornerSmall
+ ?attr/shapeAppearanceCornerMedium
+
+
1dp
?attr/textAppearanceLabelLarge
+ 20dp
+ ?attr/shapeAppearanceCornerMedium
16dp
8dp
+ 16dp
+ ?attr/shapeAppearanceCornerSmall
+ ?attr/shapeAppearanceCornerMedium
+
+
+
+ 1dp
+ ?attr/textAppearanceTitleMedium
+ 24dp
+
+ ?attr/shapeAppearanceCornerLarge
+ 24dp
+ 8dp
+ 24dp
+ ?attr/shapeAppearanceCornerMedium
+ ?attr/shapeAppearanceCornerLarge
+
+
+
+ 2dp
+ ?attr/textAppearanceHeadlineSmall
+ 32dp
+
+ ?attr/shapeAppearanceCornerExtraLarge
+ 48dp
+ 12dp
+ 48dp
+ ?attr/shapeAppearanceCornerLarge
+ ?attr/shapeAppearanceCornerExtraLarge
+
+
+
+ 3dp
+ ?attr/textAppearanceHeadlineLarge
+ 40dp
+
+ ?attr/shapeAppearanceCornerExtraLarge
+ 64dp
+ 16dp
+ 64dp
+ ?attr/shapeAppearanceCornerLarge
+ ?attr/shapeAppearanceCornerExtraLarge
+
?attr/colorPrimary
+ ?attr/colorSurfaceContainer
+ ?attr/colorPrimary
@dimen/m3_sys_elevation_level0
?attr/colorOnPrimary
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnPrimary
@dimen/m3_sys_elevation_level0
?attr/colorSecondaryContainer
+ ?attr/colorSecondaryContainer
+ ?attr/colorSecondary
@dimen/m3_sys_elevation_level0
?attr/colorOnSecondaryContainer
+ ?attr/colorOnSecondaryContainer
+ ?attr/colorOnSecondary
?attr/colorSurfaceContainerLow
+ ?attr/colorSurfaceContainerLow
+ ?attr/colorPrimary
@dimen/m3_sys_elevation_level1
?attr/colorPrimary
+ ?attr/colorPrimary
+ ?attr/colorOnPrimary
@dimen/m3_sys_elevation_level0
@@ -58,4 +128,55 @@
@dimen/m3_sys_state_pressed_state_layer_opacity
+
+
+ ?attr/colorOutlineVariant
+ ?attr/colorSurfaceInverse
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceInverse
+
+ ?attr/colorOutlineVariant
+ ?attr/colorOutlineVariant
+ ?attr/colorOnSurface
+
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceInverse
+ @dimen/m3_sys_state_hover_state_layer_opacity
+ ?attr/colorOutlineVariant
+ ?attr/colorOutlineVariant
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceInverse
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceInverse
+
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceInverse
+ @dimen/m3_sys_state_focus_state_layer_opacity
+ ?attr/colorOutlineVariant
+ ?attr/colorOutlineVariant
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceInverse
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceInverse
+
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceInverse
+ @dimen/m3_sys_state_pressed_state_layer_opacity
+ ?attr/colorOutlineVariant
+ ?attr/colorOutlineVariant
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceInverse
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSurfaceInverse
+
diff --git a/lib/java/com/google/android/material/button/res/xml/m3_split_button_inner_corner_size_state_list.xml b/lib/java/com/google/android/material/button/res/xml/m3_split_button_inner_corner_size_state_list.xml
new file mode 100644
index 00000000000..7393873af6e
--- /dev/null
+++ b/lib/java/com/google/android/material/button/res/xml/m3_split_button_inner_corner_size_state_list.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/button/res/xml/m3expressive_button_shape_state_list.xml b/lib/java/com/google/android/material/button/res/xml/m3expressive_button_shape_state_list.xml
new file mode 100644
index 00000000000..45fc5337fee
--- /dev/null
+++ b/lib/java/com/google/android/material/button/res/xml/m3expressive_button_shape_state_list.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/button/res/xml/m3expressive_connected_buttons_inner_corner_size_state_list.xml b/lib/java/com/google/android/material/button/res/xml/m3expressive_connected_buttons_inner_corner_size_state_list.xml
new file mode 100644
index 00000000000..017429b0b1f
--- /dev/null
+++ b/lib/java/com/google/android/material/button/res/xml/m3expressive_connected_buttons_inner_corner_size_state_list.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/card/MaterialCardViewHelper.java b/lib/java/com/google/android/material/card/MaterialCardViewHelper.java
index 0a28468361d..7949f76cfff 100644
--- a/lib/java/com/google/android/material/card/MaterialCardViewHelper.java
+++ b/lib/java/com/google/android/material/card/MaterialCardViewHelper.java
@@ -143,11 +143,16 @@ public MaterialCardViewHelper(
TypedArray cardViewAttributes =
card.getContext()
- .obtainStyledAttributes(attrs, R.styleable.CardView, defStyleAttr, R.style.CardView);
- if (cardViewAttributes.hasValue(R.styleable.CardView_cardCornerRadius)) {
+ .obtainStyledAttributes(
+ attrs,
+ androidx.cardview.R.styleable.CardView,
+ defStyleAttr,
+ androidx.cardview.R.style.CardView);
+ if (cardViewAttributes.hasValue(androidx.cardview.R.styleable.CardView_cardCornerRadius)) {
// If cardCornerRadius is set, let it override the shape appearance.
shapeAppearanceModelBuilder.setAllCornerSizes(
- cardViewAttributes.getDimension(R.styleable.CardView_cardCornerRadius, 0));
+ cardViewAttributes.getDimension(
+ androidx.cardview.R.styleable.CardView_cardCornerRadius, 0));
}
foregroundContentDrawable = new MaterialShapeDrawable();
@@ -199,7 +204,8 @@ void loadFromAttributes(@NonNull TypedArray attributes) {
if (rippleColor == null) {
rippleColor =
ColorStateList.valueOf(
- MaterialColors.getColor(materialCardView, R.attr.colorControlHighlight));
+ MaterialColors.getColor(
+ materialCardView, androidx.appcompat.R.attr.colorControlHighlight));
}
ColorStateList foregroundColor =
@@ -416,7 +422,7 @@ void setRippleColor(@Nullable ColorStateList rippleColor) {
void setCheckedIconTint(@Nullable ColorStateList checkedIconTint) {
this.checkedIconTint = checkedIconTint;
if (checkedIcon != null) {
- DrawableCompat.setTintList(checkedIcon, checkedIconTint);
+ checkedIcon.setTintList(checkedIconTint);
}
}
@@ -438,7 +444,7 @@ Drawable getCheckedIcon() {
void setCheckedIcon(@Nullable Drawable checkedIcon) {
if (checkedIcon != null) {
this.checkedIcon = DrawableCompat.wrap(checkedIcon).mutate();
- DrawableCompat.setTintList(this.checkedIcon, checkedIconTint);
+ this.checkedIcon.setTintList(checkedIconTint);
setChecked(materialCardView.isChecked());
} else {
this.checkedIcon = CHECKED_ICON_NONE;
diff --git a/lib/java/com/google/android/material/card/res/values/styles.xml b/lib/java/com/google/android/material/card/res/values/styles.xml
index 82770600d8f..3e6d11b7138 100644
--- a/lib/java/com/google/android/material/card/res/values/styles.xml
+++ b/lib/java/com/google/android/material/card/res/values/styles.xml
@@ -21,9 +21,7 @@
@@ -188,7 +184,7 @@
- @dimen/m3_comp_input_chip_container_height
- @dimen/m3_comp_input_chip_with_avatar_avatar_size
- @dimen/m3_comp_input_chip_unselected_outline_width
- - @dimen/m3_comp_input_chip_container_elevation
+ - @dimen/m3_comp_input_chip_container_elevation
- 4dp
- 0dp
@@ -204,12 +200,8 @@
@@ -224,12 +216,8 @@
@@ -257,7 +245,7 @@
- @dimen/m3_comp_suggestion_chip_container_height
- @dimen/m3_comp_suggestion_chip_with_leading_icon_leading_icon_size
- @dimen/m3_comp_suggestion_chip_flat_outline_width
- - @dimen/m3_comp_suggestion_chip_flat_container_elevation
+ - @dimen/m3_comp_suggestion_chip_flat_container_elevation
- 8dp
- 0dp
@@ -274,12 +262,10 @@
@@ -309,7 +295,7 @@
- @dimen/m3_comp_assist_chip_container_height
- @dimen/m3_comp_assist_chip_with_icon_icon_size
- @dimen/m3_comp_assist_chip_flat_outline_width
- - @dimen/m3_comp_assist_chip_flat_container_elevation
+ - @dimen/m3_comp_assist_chip_flat_container_elevation
- 8dp
- 0dp
@@ -326,12 +312,8 @@
@@ -348,7 +330,7 @@
- @dimen/m3_comp_filter_chip_container_height
- @dimen/m3_comp_filter_chip_with_icon_icon_size
- @dimen/m3_comp_filter_chip_flat_unselected_outline_width
- - @dimen/m3_comp_filter_chip_flat_container_elevation
+ - @dimen/m3_comp_filter_chip_flat_container_elevation
- 8dp
- 0dp
@@ -365,11 +347,8 @@
diff --git a/lib/java/com/google/android/material/chip/res/values/tokens.xml b/lib/java/com/google/android/material/chip/res/values/tokens.xml
index f397d625a3b..721c790da6d 100644
--- a/lib/java/com/google/android/material/chip/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/chip/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/ColorResourcesTableCreator.java b/lib/java/com/google/android/material/color/ColorResourcesTableCreator.java
index cabc900189b..fd911149a6a 100644
--- a/lib/java/com/google/android/material/color/ColorResourcesTableCreator.java
+++ b/lib/java/com/google/android/material/color/ColorResourcesTableCreator.java
@@ -16,12 +16,13 @@
package com.google.android.material.color;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.content.Context;
import android.util.Pair;
import androidx.annotation.ColorInt;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -77,7 +78,7 @@ static byte[] create(Context context, Map colorMapping) throws
colorResource =
new ColorResource(
entry.getKey(),
- context.getResources().getResourceName(entry.getKey()),
+ context.getResources().getResourceEntryName(entry.getKey()),
entry.getValue());
if (!context
.getResources()
@@ -338,16 +339,37 @@ private static class PackageChunk {
PackageChunk(PackageInfo packageInfo, List colorResources) {
this.packageInfo = packageInfo;
- // Placeholder String type, since only XML color resources will be replaced at runtime.
- typeStrings = new StringPoolChunk(false, "?1", "?2", "?3", "?4", "?5", "color");
+ typeStrings = new StringPoolChunk(false, generateTypeStrings(colorResources));
+ keyStrings = new StringPoolChunk(true, generateKeyStrings(colorResources));
+ typeSpecChunk = new TypeSpecChunk(colorResources);
+
+ header = new ResChunkHeader(HEADER_TYPE_PACKAGE, HEADER_SIZE, getChunkSize());
+ }
+
+ private String[] generateTypeStrings(List colorResources) {
+ if (!colorResources.isEmpty()) {
+ byte colorTypeId = colorResources.get(0).typeId;
+ String[] types = new String[colorTypeId];
+
+ // Placeholder String type, since only XML color resources will be replaced at runtime.
+ for (int i = 0; i < colorTypeId - 1; i++) {
+ types[i] = "?" + (i + 1);
+ }
+
+ types[colorTypeId - 1] = "color";
+
+ return types;
+ } else {
+ return new String[0];
+ }
+ }
+
+ private String[] generateKeyStrings(List colorResources) {
String[] keys = new String[colorResources.size()];
for (int i = 0; i < colorResources.size(); i++) {
keys[i] = colorResources.get(i).name;
}
- keyStrings = new StringPoolChunk(true, keys);
- typeSpecChunk = new TypeSpecChunk(colorResources);
-
- header = new ResChunkHeader(HEADER_TYPE_PACKAGE, HEADER_SIZE, getChunkSize());
+ return keys;
}
void writeTo(ByteArrayOutputStream outputStream) throws IOException {
@@ -609,13 +631,36 @@ private static byte[] stringToByteArray(String value) {
return bytes;
}
- private static byte[] stringToByteArrayUtf8(String value) {
- byte[] rawBytes = value.getBytes(Charset.forName("UTF-8"));
- byte stringLength = (byte) rawBytes.length;
- byte[] bytes = new byte[rawBytes.length + 3];
- System.arraycopy(rawBytes, 0, bytes, 2, stringLength);
- bytes[0] = bytes[1] = stringLength;
- bytes[bytes.length - 1] = 0; // EOS
- return bytes;
+ private static byte[] stringToByteArrayUtf8(String str) {
+ byte[] strBytes = str.getBytes(UTF_8);
+ byte[] strLengthBytes = encodeLengthUtf8((short) str.length());
+ byte[] encStrLengthBytes = encodeLengthUtf8((short) strBytes.length);
+
+ return concat(
+ strLengthBytes,
+ encStrLengthBytes,
+ strBytes,
+ new byte[] { 0 } // EOS
+ );
+ }
+
+ private static byte[] encodeLengthUtf8(short length) {
+ return length > 0x7F
+ ? new byte[] { (byte) (((length >> 8) & 0x7F) | 0x80), (byte) (length & 0xFF) }
+ : new byte[] { (byte) (length & 0xFF) };
+ }
+
+ private static byte[] concat(byte[]... arrays) {
+ int length = 0;
+ for (byte[] array : arrays) {
+ length += array.length;
+ }
+ byte[] result = new byte[length];
+ int pos = 0;
+ for (byte[] array : arrays) {
+ System.arraycopy(array, 0, result, pos, array.length);
+ pos += array.length;
+ }
+ return result;
}
}
diff --git a/lib/java/com/google/android/material/color/HarmonizedColorAttributes.java b/lib/java/com/google/android/material/color/HarmonizedColorAttributes.java
index f4c09404507..f5e6ba3b266 100644
--- a/lib/java/com/google/android/material/color/HarmonizedColorAttributes.java
+++ b/lib/java/com/google/android/material/color/HarmonizedColorAttributes.java
@@ -33,7 +33,7 @@ public final class HarmonizedColorAttributes {
private static final int[] HARMONIZED_MATERIAL_ATTRIBUTES =
new int[] {
- R.attr.colorError,
+ androidx.appcompat.R.attr.colorError,
R.attr.colorOnError,
R.attr.colorErrorContainer,
R.attr.colorOnErrorContainer
diff --git a/lib/java/com/google/android/material/color/HarmonizedColorsOptions.java b/lib/java/com/google/android/material/color/HarmonizedColorsOptions.java
index 086ee49f48f..e3e8cf8a9c2 100644
--- a/lib/java/com/google/android/material/color/HarmonizedColorsOptions.java
+++ b/lib/java/com/google/android/material/color/HarmonizedColorsOptions.java
@@ -16,8 +16,6 @@
package com.google.android.material.color;
-import com.google.android.material.R;
-
import androidx.annotation.AttrRes;
import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
@@ -80,7 +78,9 @@ public static class Builder {
@NonNull @ColorRes private int[] colorResourceIds = new int[] {};
@Nullable private HarmonizedColorAttributes colorAttributes;
- @AttrRes private int colorAttributeToHarmonizeWith = R.attr.colorPrimary;
+
+ @AttrRes
+ private int colorAttributeToHarmonizeWith = androidx.appcompat.R.attr.colorPrimary;
/**
* Sets the array of color resource ids for harmonization.
diff --git a/lib/java/com/google/android/material/color/MaterialColors.java b/lib/java/com/google/android/material/color/MaterialColors.java
index 56484495b90..05db64b51e4 100644
--- a/lib/java/com/google/android/material/color/MaterialColors.java
+++ b/lib/java/com/google/android/material/color/MaterialColors.java
@@ -15,8 +15,6 @@
*/
package com.google.android.material.color;
-import com.google.android.material.R;
-
import static android.graphics.Color.TRANSPARENT;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
@@ -263,7 +261,10 @@ public static boolean isColorLight(@ColorInt int color) {
public static int harmonizeWithPrimary(@NonNull Context context, @ColorInt int colorToHarmonize) {
return harmonize(
colorToHarmonize,
- getColor(context, R.attr.colorPrimary, MaterialColors.class.getCanonicalName()));
+ getColor(
+ context,
+ androidx.appcompat.R.attr.colorPrimary,
+ MaterialColors.class.getCanonicalName()));
}
/**
@@ -348,7 +349,7 @@ public static int getSurfaceContainerHighFromSeed(
static boolean isLightTheme(@NonNull Context context) {
return MaterialAttributes.resolveBoolean(
- context, R.attr.isLightTheme, /* defaultValue= */ true);
+ context, androidx.appcompat.R.attr.isLightTheme, /* defaultValue= */ true);
}
@ColorInt
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral12.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral12.xml
index bf5d122582e..644e12ca9d7 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral12.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral12.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral17.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral17.xml
index da70cf79b2f..07bd697788f 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral17.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral17.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral22.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral22.xml
index 916cab8d54d..18574988da5 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral22.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral22.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral24.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral24.xml
index 579bc5dc579..2ce03ab83f1 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral24.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral24.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral4.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral4.xml
index 9796e28d99d..2ecdf95992f 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral4.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral4.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral6.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral6.xml
index 1a65025b7c4..9326bc100e3 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral6.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral6.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral87.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral87.xml
index 8ffe132f5d1..2ec21948d96 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral87.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral87.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral92.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral92.xml
index 308b8a31181..225e4ad738d 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral92.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral92.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral94.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral94.xml
index c5b6f4af913..20882ad6605 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral94.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral94.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral96.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral96.xml
index bd3ca2ab5f9..a999076ad76 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral96.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral96.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral98.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral98.xml
index 440397550f4..fd896c8b70d 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral98.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral98.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral_variant98.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral_variant98.xml
index c17490ec53d..1f6f99918a4 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral_variant98.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral_variant98.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_primary98.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_primary98.xml
index 062496d17e7..95fa8455e8b 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_primary98.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_primary98.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_secondary98.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_secondary98.xml
index 8a7b4677bad..4cce9e3517d 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_secondary98.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_secondary98.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_tertiary98.xml b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_tertiary98.xml
index 318dc005068..e5d4567095b 100644
--- a/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_tertiary98.xml
+++ b/lib/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_tertiary98.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color/m3_highlighted_text.xml b/lib/java/com/google/android/material/color/res/color/m3_highlighted_text.xml
index dd3ad371651..ab960c74a58 100644
--- a/lib/java/com/google/android/material/color/res/color/m3_highlighted_text.xml
+++ b/lib/java/com/google/android/material/color/res/color/m3_highlighted_text.xml
@@ -16,5 +16,5 @@
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/color/m3_standard_toolbar_button_text_color_selector.xml b/lib/java/com/google/android/material/color/res/color/m3_standard_toolbar_button_text_color_selector.xml
new file mode 100644
index 00000000000..b0a4016b272
--- /dev/null
+++ b/lib/java/com/google/android/material/color/res/color/m3_standard_toolbar_button_text_color_selector.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/color/res/color/m3_standard_toolbar_icon_button_container_color_selector.xml b/lib/java/com/google/android/material/color/res/color/m3_standard_toolbar_icon_button_container_color_selector.xml
new file mode 100644
index 00000000000..039ee044bf1
--- /dev/null
+++ b/lib/java/com/google/android/material/color/res/color/m3_standard_toolbar_icon_button_container_color_selector.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/color/res/color/m3_standard_toolbar_icon_button_icon_color_selector.xml b/lib/java/com/google/android/material/color/res/color/m3_standard_toolbar_icon_button_icon_color_selector.xml
new file mode 100644
index 00000000000..4e398f05229
--- /dev/null
+++ b/lib/java/com/google/android/material/color/res/color/m3_standard_toolbar_icon_button_icon_color_selector.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/color/res/color/m3_standard_toolbar_icon_button_ripple_color_selector.xml b/lib/java/com/google/android/material/color/res/color/m3_standard_toolbar_icon_button_ripple_color_selector.xml
new file mode 100644
index 00000000000..c1661f8889b
--- /dev/null
+++ b/lib/java/com/google/android/material/color/res/color/m3_standard_toolbar_icon_button_ripple_color_selector.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/color/res/color/m3_vibrant_toolbar_button_text_color_selector.xml b/lib/java/com/google/android/material/color/res/color/m3_vibrant_toolbar_button_text_color_selector.xml
new file mode 100644
index 00000000000..da290da4893
--- /dev/null
+++ b/lib/java/com/google/android/material/color/res/color/m3_vibrant_toolbar_button_text_color_selector.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/color/res/color/m3_vibrant_toolbar_icon_button_container_color_selector.xml b/lib/java/com/google/android/material/color/res/color/m3_vibrant_toolbar_icon_button_container_color_selector.xml
new file mode 100644
index 00000000000..a601d428d7a
--- /dev/null
+++ b/lib/java/com/google/android/material/color/res/color/m3_vibrant_toolbar_icon_button_container_color_selector.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/color/res/color/m3_vibrant_toolbar_icon_button_icon_color_selector.xml b/lib/java/com/google/android/material/color/res/color/m3_vibrant_toolbar_icon_button_icon_color_selector.xml
new file mode 100644
index 00000000000..a716747eef3
--- /dev/null
+++ b/lib/java/com/google/android/material/color/res/color/m3_vibrant_toolbar_icon_button_icon_color_selector.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/color/res/color/m3_vibrant_toolbar_icon_button_ripple_color_selector.xml b/lib/java/com/google/android/material/color/res/color/m3_vibrant_toolbar_icon_button_ripple_color_selector.xml
new file mode 100644
index 00000000000..0b3fcc2b05c
--- /dev/null
+++ b/lib/java/com/google/android/material/color/res/color/m3_vibrant_toolbar_icon_button_ripple_color_selector.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/color/res/values-v31/tokens.xml b/lib/java/com/google/android/material/color/res/values-v31/tokens.xml
index 2849e5df198..2ab9bc15ddf 100644
--- a/lib/java/com/google/android/material/color/res/values-v31/tokens.xml
+++ b/lib/java/com/google/android/material/color/res/values-v31/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/values-v34/tokens.xml b/lib/java/com/google/android/material/color/res/values-v34/tokens.xml
index f2fb8b197ab..855db952e37 100644
--- a/lib/java/com/google/android/material/color/res/values-v34/tokens.xml
+++ b/lib/java/com/google/android/material/color/res/values-v34/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/values-v35/tokens.xml b/lib/java/com/google/android/material/color/res/values-v35/tokens.xml
index 9c7b55adad5..36de1c04749 100644
--- a/lib/java/com/google/android/material/color/res/values-v35/tokens.xml
+++ b/lib/java/com/google/android/material/color/res/values-v35/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/values/extended_palette_tokens.xml b/lib/java/com/google/android/material/color/res/values/extended_palette_tokens.xml
index 7d91c297ab2..7f1a605d5a4 100644
--- a/lib/java/com/google/android/material/color/res/values/extended_palette_tokens.xml
+++ b/lib/java/com/google/android/material/color/res/values/extended_palette_tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/color/res/values/theme_overlay.xml b/lib/java/com/google/android/material/color/res/values/theme_overlay.xml
index d9a962736ae..04e76534695 100644
--- a/lib/java/com/google/android/material/color/res/values/theme_overlay.xml
+++ b/lib/java/com/google/android/material/color/res/values/theme_overlay.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -79,7 +177,7 @@
@@ -91,87 +189,17 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/lib/java/com/google/android/material/dialog/res/values/themes.xml b/lib/java/com/google/android/material/dialog/res/values/themes.xml
index 64bdea3b9d9..52bab99144c 100644
--- a/lib/java/com/google/android/material/dialog/res/values/themes.xml
+++ b/lib/java/com/google/android/material/dialog/res/values/themes.xml
@@ -15,43 +15,48 @@
~ limitations under the License.
-->
-
+
-
-
-
+
+
-
+
-
+
-
-
-
+
-
-
+
-
@@ -67,7 +72,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/dialog/res/values/themes_base.xml b/lib/java/com/google/android/material/dialog/res/values/themes_base.xml
index 6f9efc129b3..be3fc2fbbf1 100644
--- a/lib/java/com/google/android/material/dialog/res/values/themes_base.xml
+++ b/lib/java/com/google/android/material/dialog/res/values/themes_base.xml
@@ -19,6 +19,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/lib/java/com/google/android/material/dialog/res/values/tokens.xml b/lib/java/com/google/android/material/dialog/res/values/tokens.xml
index 551c66bab45..8062a426556 100644
--- a/lib/java/com/google/android/material/dialog/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/dialog/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/divider/MaterialDividerItemDecoration.java b/lib/java/com/google/android/material/divider/MaterialDividerItemDecoration.java
index 291969c22cc..8ddb88a350a 100644
--- a/lib/java/com/google/android/material/divider/MaterialDividerItemDecoration.java
+++ b/lib/java/com/google/android/material/divider/MaterialDividerItemDecoration.java
@@ -176,7 +176,7 @@ public int getDividerThickness() {
public void setDividerColor(@ColorInt int color) {
this.color = color;
dividerDrawable = DrawableCompat.wrap(dividerDrawable);
- DrawableCompat.setTint(dividerDrawable, color);
+ dividerDrawable.setTint(color);
}
/**
diff --git a/lib/java/com/google/android/material/divider/res/values/tokens.xml b/lib/java/com/google/android/material/divider/res/values/tokens.xml
index b62dad10a60..b0539327f02 100644
--- a/lib/java/com/google/android/material/divider/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/divider/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/dockedtoolbar/DockedToolbarLayout.java b/lib/java/com/google/android/material/dockedtoolbar/DockedToolbarLayout.java
new file mode 100644
index 00000000000..2a69c9c68ad
--- /dev/null
+++ b/lib/java/com/google/android/material/dockedtoolbar/DockedToolbarLayout.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2025 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.dockedtoolbar;
+
+import com.google.android.material.R;
+
+import static com.google.android.material.theme.overlay.MaterialThemeOverlay.wrap;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import androidx.appcompat.widget.TintTypedArray;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import androidx.annotation.AttrRes;
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StyleRes;
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import androidx.core.graphics.Insets;
+import androidx.core.view.WindowInsetsCompat;
+import com.google.android.material.internal.ThemeEnforcement;
+import com.google.android.material.internal.ViewUtils;
+import com.google.android.material.internal.ViewUtils.RelativePadding;
+import com.google.android.material.shape.MaterialShapeDrawable;
+import com.google.android.material.shape.ShapeAppearanceModel;
+
+/**
+ * Provides an implementation of a docked toolbar.
+ *
+ * Docked toolbars are pinned to the top or bottom and can be used to display contextual actions
+ * relevant to the body content or the specific page.
+ *
+ *
The docked toolbar supports a custom {@link android.view.ViewGroup} child, and provides docked
+ * toolbar styling such as background color, shape, etc.
+ */
+public class DockedToolbarLayout extends FrameLayout {
+
+ private static final String TAG = DockedToolbarLayout.class.getSimpleName();
+ private static final int DEF_STYLE_RES = R.style.Widget_Material3_DockedToolbar;
+ private Boolean paddingTopSystemWindowInsets;
+ private Boolean paddingBottomSystemWindowInsets;
+
+ public DockedToolbarLayout(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public DockedToolbarLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, R.attr.dockedToolbarStyle);
+ }
+
+ public DockedToolbarLayout(
+ @NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
+ this(context, attrs, defStyleAttr, DEF_STYLE_RES);
+ }
+
+ public DockedToolbarLayout(
+ @NonNull Context context,
+ @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr,
+ @StyleRes int defStyleRes) {
+ super(wrap(context, attrs, defStyleAttr, defStyleRes), attrs, defStyleAttr);
+
+ // Ensure we are using the correctly themed context rather than the context that was passed in.
+ context = getContext();
+
+ /* Custom attributes */
+ TintTypedArray attributes =
+ ThemeEnforcement.obtainTintedStyledAttributes(
+ context, attrs, R.styleable.DockedToolbar, defStyleAttr, defStyleRes);
+
+ // Add a MaterialShapeDrawable as a background that supports tinting in every API level.
+ if (attributes.hasValue(R.styleable.DockedToolbar_backgroundTint)) {
+ @ColorInt
+ int backgroundColor = attributes.getColor(R.styleable.DockedToolbar_backgroundTint, 0);
+
+ ShapeAppearanceModel shapeAppearanceModel =
+ ShapeAppearanceModel.builder(context, attrs, defStyleAttr, defStyleRes).build();
+ MaterialShapeDrawable materialShapeDrawable = new MaterialShapeDrawable(shapeAppearanceModel);
+ materialShapeDrawable.setFillColor(ColorStateList.valueOf(backgroundColor));
+
+ setBackground(materialShapeDrawable);
+ }
+
+ // Reading out if we are handling inset padding, so we can apply it to the content.
+ if (attributes.hasValue(R.styleable.DockedToolbar_paddingTopSystemWindowInsets)) {
+ paddingTopSystemWindowInsets =
+ attributes.getBoolean(R.styleable.DockedToolbar_paddingTopSystemWindowInsets, true);
+ }
+ if (attributes.hasValue(R.styleable.DockedToolbar_paddingBottomSystemWindowInsets)) {
+ paddingBottomSystemWindowInsets =
+ attributes.getBoolean(R.styleable.DockedToolbar_paddingBottomSystemWindowInsets, true);
+ }
+
+ ViewUtils.doOnApplyWindowInsets(
+ this,
+ new ViewUtils.OnApplyWindowInsetsListener() {
+ @NonNull
+ @Override
+ public WindowInsetsCompat onApplyWindowInsets(
+ View view,
+ @NonNull WindowInsetsCompat insets,
+ @NonNull RelativePadding initialPadding) {
+ if (paddingTopSystemWindowInsets != null
+ && paddingBottomSystemWindowInsets != null
+ && !paddingTopSystemWindowInsets
+ && !paddingBottomSystemWindowInsets) {
+ return insets;
+ }
+ Insets systemBarInsets =
+ insets.getInsets(
+ WindowInsetsCompat.Type.systemBars()
+ | WindowInsetsCompat.Type.displayCutout()
+ | WindowInsetsCompat.Type.ime());
+ int bottomInset = systemBarInsets.bottom;
+ int topInset = systemBarInsets.top;
+ int bottomPadding = 0;
+ int topPadding = 0;
+
+ ViewGroup.LayoutParams lp = view.getLayoutParams();
+ // If the inset flags are not explicitly set, and the toolbar is inside a
+ // CoordinatorLayout or a FrameLayout, we can use the gravity
+ // to ascertain what padding should be automatically added.
+ if (hasGravity(lp, Gravity.TOP) && paddingTopSystemWindowInsets == null && getFitsSystemWindows()) {
+ topPadding = topInset;
+ }
+ if (hasGravity(lp, Gravity.BOTTOM) && paddingBottomSystemWindowInsets == null && getFitsSystemWindows()) {
+ bottomPadding = bottomInset;
+ }
+
+ // If paddingTopSystemWindowInsets or paddingBottomSystemWindowInsets is explicitly
+ // set, then insets should always be applied to the padding.
+ if (paddingBottomSystemWindowInsets != null) {
+ bottomPadding = paddingBottomSystemWindowInsets ? bottomInset : 0;
+ }
+ if (paddingTopSystemWindowInsets != null) {
+ topPadding = paddingTopSystemWindowInsets ? topInset : 0;
+ }
+ initialPadding.top += topPadding;
+ initialPadding.bottom += bottomPadding;
+ initialPadding.applyToView(view);
+
+ return insets;
+ }
+ });
+
+ setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ attributes.recycle();
+ }
+
+ private boolean hasGravity(ViewGroup.LayoutParams lp, int gravity) {
+ if (lp instanceof CoordinatorLayout.LayoutParams) {
+ return (((CoordinatorLayout.LayoutParams) lp).gravity & gravity) == gravity;
+ } else if (lp instanceof FrameLayout.LayoutParams) {
+ return (((FrameLayout.LayoutParams) lp).gravity & gravity) == gravity;
+ }
+ return false;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
+ int childCount = getChildCount();
+ int newHeight =
+ Math.max(
+ getMeasuredHeight(),
+ getSuggestedMinimumHeight() + getPaddingTop() + getPaddingBottom());
+
+ for (int i = 0; i < childCount; i++) {
+ measureChild(
+ getChildAt(i),
+ widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
+ }
+
+ setMeasuredDimension(getMeasuredWidth(), newHeight);
+ }
+ }
+}
diff --git a/lib/java/com/google/android/material/snackbar/res/values/strings.xml b/lib/java/com/google/android/material/dockedtoolbar/res-public/values/public.xml
similarity index 67%
rename from lib/java/com/google/android/material/snackbar/res/values/strings.xml
rename to lib/java/com/google/android/material/dockedtoolbar/res-public/values/public.xml
index 75b159a9518..82eea3380e7 100644
--- a/lib/java/com/google/android/material/snackbar/res/values/strings.xml
+++ b/lib/java/com/google/android/material/dockedtoolbar/res-public/values/public.xml
@@ -1,6 +1,6 @@
-
-
- Alert
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/dockedtoolbar/res/values/attrs.xml b/lib/java/com/google/android/material/dockedtoolbar/res/values/attrs.xml
new file mode 100644
index 00000000000..beca0bac9f8
--- /dev/null
+++ b/lib/java/com/google/android/material/dockedtoolbar/res/values/attrs.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/dockedtoolbar/res/values/styles.xml b/lib/java/com/google/android/material/dockedtoolbar/res/values/styles.xml
new file mode 100644
index 00000000000..7e4905ad698
--- /dev/null
+++ b/lib/java/com/google/android/material/dockedtoolbar/res/values/styles.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/dockedtoolbar/res/values/tokens.xml b/lib/java/com/google/android/material/dockedtoolbar/res/values/tokens.xml
new file mode 100644
index 00000000000..a4df4cdc776
--- /dev/null
+++ b/lib/java/com/google/android/material/dockedtoolbar/res/values/tokens.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ 64dp
+ 16dp
+ 16dp
+
+
+
+
diff --git a/lib/java/com/google/android/material/drawable/DrawableUtils.java b/lib/java/com/google/android/material/drawable/DrawableUtils.java
index 0525ea0cb70..fb6cf91098f 100644
--- a/lib/java/com/google/android/material/drawable/DrawableUtils.java
+++ b/lib/java/com/google/android/material/drawable/DrawableUtils.java
@@ -99,9 +99,9 @@ public static void setTint(@NonNull Drawable drawable, @ColorInt int color) {
}
} else {
if (hasTint) {
- DrawableCompat.setTint(drawable, color);
+ drawable.setTint(color);
} else {
- DrawableCompat.setTintList(drawable, null);
+ drawable.setTintList(null);
}
}
}
@@ -203,7 +203,7 @@ private static Drawable createTintableMutatedDrawableIfNeeded(
if (tintList != null) {
drawable = DrawableCompat.wrap(drawable).mutate();
if (tintMode != null) {
- DrawableCompat.setTintMode(drawable, tintMode);
+ drawable.setTintMode(tintMode);
}
} else if (forceMutate) {
drawable.mutate();
diff --git a/lib/java/com/google/android/material/elevation/res/values/tokens.xml b/lib/java/com/google/android/material/elevation/res/values/tokens.xml
index bdb2a484086..e1ca7ff8ac0 100644
--- a/lib/java/com/google/android/material/elevation/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/elevation/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/floatingactionbutton/ExtendedFloatingActionButton.java b/lib/java/com/google/android/material/floatingactionbutton/ExtendedFloatingActionButton.java
index ae6beabab2b..bfe9e5de906 100644
--- a/lib/java/com/google/android/material/floatingactionbutton/ExtendedFloatingActionButton.java
+++ b/lib/java/com/google/android/material/floatingactionbutton/ExtendedFloatingActionButton.java
@@ -51,7 +51,6 @@
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.coordinatorlayout.widget.CoordinatorLayout.AttachedBehavior;
import androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior;
-import androidx.core.view.ViewCompat;
import com.google.android.material.animation.MotionSpec;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
@@ -984,7 +983,7 @@ public Float get(@NonNull View object) {
/**
* A Property wrapper around the paddingStart
functionality handled by the {@link
- * ViewCompat#setPaddingRelative(View, int, int, int, int)}.
+ * View#setPaddingRelative(int, int, int, int)}.
*/
static final Property PADDING_START =
new Property(Float.class, "paddingStart") {
@@ -1006,7 +1005,7 @@ public Float get(@NonNull View object) {
/**
* A Property wrapper around the paddingEnd
functionality handled by the {@link
- * ViewCompat#setPaddingRelative(View, int, int, int, int)}.
+ * View#setPaddingRelative(int, int, int, int)}.
*/
static final Property PADDING_END =
new Property(Float.class, "paddingEnd") {
diff --git a/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButton.java b/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButton.java
index b799ed42c22..817cbb80631 100644
--- a/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButton.java
+++ b/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButton.java
@@ -32,6 +32,8 @@
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Parcelable;
import androidx.appcompat.widget.AppCompatDrawableManager;
@@ -837,13 +839,15 @@ protected void onDetachedFromWindow() {
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
- getImpl().onDrawableStateChanged(getDrawableState());
+ if (Build.VERSION.SDK_INT == VERSION_CODES.LOLLIPOP) {
+ getImpl().onDrawableStateChangedForLollipop();
+ }
}
+ @SuppressWarnings("RedundantOverride")
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
- getImpl().jumpDrawableToCurrentState();
}
@Override
@@ -1048,34 +1052,34 @@ public void setInternalAutoHideListener(OnVisibilityChangedListener listener) {
// dereference of possibly-null reference lp
@SuppressWarnings("nullness:dereference.of.nullable")
- private boolean shouldUpdateVisibility(
+ private boolean ignoreUpdateVisibility(
@NonNull View dependency, @NonNull FloatingActionButton child) {
final CoordinatorLayout.LayoutParams lp =
(CoordinatorLayout.LayoutParams) child.getLayoutParams();
if (!autoHideEnabled) {
- return false;
+ return true;
}
if (lp.getAnchorId() != dependency.getId()) {
// The anchor ID doesn't match the dependency, so we won't automatically
// show/hide the FAB
- return false;
+ return true;
}
//noinspection RedundantIfStatement
if (child.getUserSetVisibility() != VISIBLE) {
// The view isn't set to be visible so skip changing its visibility
- return false;
+ return true;
}
- return true;
+ return false;
}
private boolean updateFabVisibilityForAppBarLayout(
CoordinatorLayout parent,
@NonNull AppBarLayout appBarLayout,
@NonNull FloatingActionButton child) {
- if (!shouldUpdateVisibility(appBarLayout, child)) {
+ if (ignoreUpdateVisibility(appBarLayout, child)) {
return false;
}
@@ -1101,7 +1105,7 @@ private boolean updateFabVisibilityForAppBarLayout(
@SuppressWarnings("nullness:dereference.of.nullable")
private boolean updateFabVisibilityForBottomSheet(
@NonNull View bottomSheet, @NonNull FloatingActionButton child) {
- if (!shouldUpdateVisibility(bottomSheet, child)) {
+ if (ignoreUpdateVisibility(bottomSheet, child)) {
return false;
}
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
@@ -1166,7 +1170,7 @@ private void offsetIfNeeded(
@NonNull CoordinatorLayout parent, @NonNull FloatingActionButton fab) {
final Rect padding = fab.shadowPadding;
- if (padding != null && padding.centerX() > 0 && padding.centerY() > 0) {
+ if (padding.centerX() > 0 && padding.centerY() > 0) {
final CoordinatorLayout.LayoutParams lp =
(CoordinatorLayout.LayoutParams) fab.getLayoutParams();
@@ -1363,7 +1367,7 @@ public void setHideMotionSpecResource(@AnimatorRes int id) {
/** Add a {@link TransformationCallback} which can watch for changes to this view. */
public void addTransformationCallback(
@NonNull TransformationCallback extends FloatingActionButton> listener) {
- getImpl().addTransformationCallback(new TransformationCallbackWrapper(listener));
+ getImpl().addTransformationCallback(new TransformationCallbackWrapper<>(listener));
}
/**
@@ -1372,7 +1376,7 @@ public void addTransformationCallback(
*/
public void removeTransformationCallback(
@NonNull TransformationCallback extends FloatingActionButton> listener) {
- getImpl().removeTransformationCallback(new TransformationCallbackWrapper(listener));
+ getImpl().removeTransformationCallback(new TransformationCallbackWrapper<>(listener));
}
class TransformationCallbackWrapper
@@ -1449,7 +1453,7 @@ public void setShadowPaddingEnabled(boolean shadowPaddingEnabled) {
private FloatingActionButtonImpl getImpl() {
if (impl == null) {
- impl = new FloatingActionButtonImplLollipop(this, new ShadowDelegateImpl());
+ impl = new FloatingActionButtonImpl(this, new ShadowDelegateImpl());
}
return impl;
}
diff --git a/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButtonImpl.java b/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButtonImpl.java
index 6bdca165fd0..a74f499be66 100644
--- a/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButtonImpl.java
+++ b/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButtonImpl.java
@@ -19,6 +19,7 @@
import com.google.android.material.R;
import static androidx.core.util.Preconditions.checkNotNull;
+import static java.lang.Math.max;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
@@ -26,12 +27,12 @@
import android.animation.AnimatorSet;
import android.animation.FloatEvaluator;
import android.animation.ObjectAnimator;
+import android.animation.StateListAnimator;
import android.animation.TimeInterpolator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
import android.content.res.ColorStateList;
-import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
import android.graphics.PorterDuff;
@@ -40,21 +41,21 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.RippleDrawable;
import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.view.View;
import android.view.ViewTreeObserver;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.core.content.ContextCompat;
import androidx.core.util.Preconditions;
import com.google.android.material.animation.AnimationUtils;
import com.google.android.material.animation.AnimatorSetCompat;
import com.google.android.material.animation.ImageMatrixProperty;
import com.google.android.material.animation.MatrixEvaluator;
import com.google.android.material.animation.MotionSpec;
-import com.google.android.material.internal.StateListAnimator;
import com.google.android.material.motion.MotionUtils;
-import com.google.android.material.ripple.RippleDrawableCompat;
import com.google.android.material.ripple.RippleUtils;
import com.google.android.material.shadow.ShadowViewDelegate;
import com.google.android.material.shape.MaterialShapeDrawable;
@@ -105,13 +106,12 @@ class FloatingActionButtonImpl {
float pressedTranslationZ;
int minTouchTargetSize;
- @NonNull private final StateListAnimator stateListAnimator;
+ @Nullable private StateListAnimator stateListAnimator;
@Nullable private Animator currentAnimator;
@Nullable private MotionSpec showMotionSpec;
@Nullable private MotionSpec hideMotionSpec;
- private float rotation;
private float imageMatrixScale = 1f;
private int maxImageSize;
private int animState = ANIM_STATE_NONE;
@@ -134,16 +134,16 @@ interface InternalVisibilityChangedListener {
}
static final int[] PRESSED_ENABLED_STATE_SET = {
- android.R.attr.state_pressed, android.R.attr.state_enabled
+ android.R.attr.state_pressed, android.R.attr.state_enabled
};
static final int[] HOVERED_FOCUSED_ENABLED_STATE_SET = {
- android.R.attr.state_hovered, android.R.attr.state_focused, android.R.attr.state_enabled
+ android.R.attr.state_hovered, android.R.attr.state_focused, android.R.attr.state_enabled
};
static final int[] FOCUSED_ENABLED_STATE_SET = {
- android.R.attr.state_focused, android.R.attr.state_enabled
+ android.R.attr.state_focused, android.R.attr.state_enabled
};
static final int[] HOVERED_ENABLED_STATE_SET = {
- android.R.attr.state_hovered, android.R.attr.state_enabled
+ android.R.attr.state_hovered, android.R.attr.state_enabled
};
static final int[] ENABLED_STATE_SET = {android.R.attr.state_enabled};
static final int[] EMPTY_STATE_SET = new int[0];
@@ -156,37 +156,12 @@ interface InternalVisibilityChangedListener {
private final RectF tmpRectF2 = new RectF();
private final Matrix tmpMatrix = new Matrix();
- @Nullable
- private ViewTreeObserver.OnPreDrawListener preDrawListener;
+ @Nullable private ViewTreeObserver.OnPreDrawListener preDrawListener;
@SuppressWarnings("nullness")
FloatingActionButtonImpl(FloatingActionButton view, ShadowViewDelegate shadowViewDelegate) {
this.view = view;
this.shadowViewDelegate = shadowViewDelegate;
-
- stateListAnimator = new StateListAnimator();
-
- // Elevate with translationZ when pressed, focused, or hovered
- stateListAnimator.addState(
- PRESSED_ENABLED_STATE_SET,
- createElevationAnimator(new ElevateToPressedTranslationZAnimation()));
- stateListAnimator.addState(
- HOVERED_FOCUSED_ENABLED_STATE_SET,
- createElevationAnimator(new ElevateToHoveredFocusedTranslationZAnimation()));
- stateListAnimator.addState(
- FOCUSED_ENABLED_STATE_SET,
- createElevationAnimator(new ElevateToHoveredFocusedTranslationZAnimation()));
- stateListAnimator.addState(
- HOVERED_ENABLED_STATE_SET,
- createElevationAnimator(new ElevateToHoveredFocusedTranslationZAnimation()));
- // Reset back to elevation by default
- stateListAnimator.addState(
- ENABLED_STATE_SET, createElevationAnimator(new ResetElevationAnimation()));
- // Set to 0 when disabled
- stateListAnimator.addState(
- EMPTY_STATE_SET, createElevationAnimator(new DisabledElevationAnimation()));
-
- rotation = this.view.getRotation();
}
void initializeBackgroundDrawable(
@@ -194,27 +169,30 @@ void initializeBackgroundDrawable(
@Nullable PorterDuff.Mode backgroundTintMode,
ColorStateList rippleColor,
int borderWidth) {
- // Now we need to tint the original background with the tint, using
- // an InsetDrawable if we have a border width
+ // Now we need to tint the shape background with the tint
shapeDrawable = createShapeDrawable();
shapeDrawable.setTintList(backgroundTint);
if (backgroundTintMode != null) {
shapeDrawable.setTintMode(backgroundTintMode);
}
-
- shapeDrawable.setShadowColor(Color.DKGRAY);
shapeDrawable.initializeElevationOverlay(view.getContext());
- // Now we created a mask Drawable which will be used for touch feedback.
- RippleDrawableCompat touchFeedbackShape =
- new RippleDrawableCompat(shapeDrawable.getShapeAppearanceModel());
- touchFeedbackShape.setTintList(RippleUtils.sanitizeRippleDrawableColor(rippleColor));
- rippleDrawable = touchFeedbackShape;
+ final Drawable rippleContent;
+ if (borderWidth > 0) {
+ borderDrawable = createBorderDrawable(borderWidth, backgroundTint);
+ rippleContent =
+ new LayerDrawable(
+ new Drawable[] {checkNotNull(borderDrawable), checkNotNull(shapeDrawable)});
+ } else {
+ borderDrawable = null;
+ rippleContent = shapeDrawable;
+ }
- final Drawable[] layers = new Drawable[]{
- checkNotNull(shapeDrawable),
- touchFeedbackShape};
- contentBackground = new LayerDrawable(layers);
+ rippleDrawable =
+ new RippleDrawable(
+ RippleUtils.sanitizeRippleDrawableColor(rippleColor), rippleContent, null);
+
+ contentBackground = rippleDrawable;
}
void setBackgroundTintList(@Nullable ColorStateList tint) {
@@ -237,9 +215,11 @@ void setMinTouchTargetSize(int minTouchTargetSize) {
}
void setRippleColor(@Nullable ColorStateList rippleColor) {
- if (rippleDrawable != null) {
- DrawableCompat.setTintList(
- rippleDrawable, RippleUtils.sanitizeRippleDrawableColor(rippleColor));
+ if (rippleDrawable instanceof RippleDrawable) {
+ ((RippleDrawable) rippleDrawable).setColor(
+ RippleUtils.sanitizeRippleDrawableColor(rippleColor));
+ } else if (rippleDrawable != null) {
+ rippleDrawable.setTintList(RippleUtils.sanitizeRippleDrawableColor(rippleColor));
}
}
@@ -251,7 +231,7 @@ final void setElevation(float elevation) {
}
float getElevation() {
- return elevation;
+ return view.getElevation();
}
float getHoveredFocusedTranslationZ() {
@@ -352,8 +332,8 @@ final void setHideMotionSpec(@Nullable MotionSpec spec) {
hideMotionSpec = spec;
}
- final boolean shouldExpandBoundsForA11y() {
- return !ensureMinTouchTargetSize || view.getSizeDimension() >= minTouchTargetSize;
+ final boolean ignoreExpandBoundsForA11y() {
+ return ensureMinTouchTargetSize && view.getSizeDimension() < minTouchTargetSize;
}
boolean getEnsureMinTouchTargetSize() {
@@ -371,11 +351,65 @@ void setShadowPaddingEnabled(boolean shadowPaddingEnabled) {
void onElevationsChanged(
float elevation, float hoveredFocusedTranslationZ, float pressedTranslationZ) {
- // If there is currently a state animation, we want to end the animation by jumping
- // the drawable to the current state before changing the elevation.
- jumpDrawableToCurrentState();
- updatePadding();
- updateShapeElevation(elevation);
+ if (Build.VERSION.SDK_INT == VERSION_CODES.LOLLIPOP) {
+ // Animations produce NPE in version 21. Bluntly set the values instead in
+ // #onDrawableStateChanged (matching the logic in the animations below).
+ view.refreshDrawableState();
+ } else if (view.getStateListAnimator() == stateListAnimator) {
+ // FAB is using the default StateListAnimator created here. Updates it with the new elevation.
+ stateListAnimator =
+ createDefaultStateListAnimator(
+ elevation, hoveredFocusedTranslationZ, pressedTranslationZ);
+ view.setStateListAnimator(stateListAnimator);
+ }
+
+ if (shouldAddPadding()) {
+ updatePadding();
+ }
+ }
+
+ @NonNull
+ private StateListAnimator createDefaultStateListAnimator(
+ final float elevation,
+ final float hoveredFocusedTranslationZ,
+ final float pressedTranslationZ) {
+ StateListAnimator stateListAnimator = new StateListAnimator();
+
+ // Animate elevation and translationZ to our values when pressed, focused, and hovered
+ stateListAnimator.addState(
+ PRESSED_ENABLED_STATE_SET, createElevationAnimator(elevation, pressedTranslationZ));
+ stateListAnimator.addState(
+ HOVERED_FOCUSED_ENABLED_STATE_SET,
+ createElevationAnimator(elevation, hoveredFocusedTranslationZ));
+ stateListAnimator.addState(
+ FOCUSED_ENABLED_STATE_SET, createElevationAnimator(elevation, hoveredFocusedTranslationZ));
+ stateListAnimator.addState(
+ HOVERED_ENABLED_STATE_SET, createElevationAnimator(elevation, hoveredFocusedTranslationZ));
+
+ // Animate translationZ to 0 if not pressed, focused, or hovered
+ AnimatorSet set = new AnimatorSet();
+ List animators = new ArrayList<>();
+ animators.add(ObjectAnimator.ofFloat(view, "elevation", elevation).setDuration(0));
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP_MR1
+ && Build.VERSION.SDK_INT <= VERSION_CODES.N) {
+ // This is a no-op animation which exists here only for introducing the duration
+ // because setting the delay (on the next animation) via "setDelay" or "after"
+ // can trigger a NPE between android versions 22 and 24 (due to a framework
+ // bug). The issue has been fixed in version 25.
+ animators.add(
+ ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, view.getTranslationZ())
+ .setDuration(ELEVATION_ANIM_DELAY));
+ }
+ animators.add(
+ ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, 0f).setDuration(ELEVATION_ANIM_DURATION));
+ set.playSequentially(animators.toArray(new Animator[0]));
+ set.setInterpolator(ELEVATION_ANIM_INTERPOLATOR);
+ stateListAnimator.addState(ENABLED_STATE_SET, set);
+
+ // Animate everything to 0 when disabled
+ stateListAnimator.addState(EMPTY_STATE_SET, createElevationAnimator(0f, 0f));
+
+ return stateListAnimator;
}
void updateShapeElevation(float elevation) {
@@ -384,12 +418,20 @@ void updateShapeElevation(float elevation) {
}
}
- void onDrawableStateChanged(int[] state) {
- stateListAnimator.setState(state);
- }
-
- void jumpDrawableToCurrentState() {
- stateListAnimator.jumpToCurrentState();
+ void onDrawableStateChangedForLollipop() {
+ if (view.isEnabled()) {
+ view.setElevation(elevation);
+ if (view.isPressed()) {
+ view.setTranslationZ(pressedTranslationZ);
+ } else if (view.isFocused() || view.isHovered()) {
+ view.setTranslationZ(hoveredFocusedTranslationZ);
+ } else {
+ view.setTranslationZ(0);
+ }
+ } else {
+ view.setElevation(0);
+ view.setTranslationZ(0);
+ }
}
void addOnShowAnimationListener(@NonNull AnimatorListener listener) {
@@ -437,12 +479,7 @@ void hide(@Nullable final InternalVisibilityChangedListener listener, final bool
if (shouldAnimateVisibilityChange()) {
AnimatorSet set;
if (hideMotionSpec != null) {
- set =
- createAnimator(
- hideMotionSpec,
- HIDE_OPACITY,
- SPEC_HIDE_SCALE,
- SPEC_HIDE_ICON_SCALE);
+ set = createAnimator(hideMotionSpec, HIDE_OPACITY, SPEC_HIDE_SCALE, SPEC_HIDE_ICON_SCALE);
} else {
set =
createDefaultAnimator(
@@ -520,15 +557,9 @@ void show(@Nullable final InternalVisibilityChangedListener listener, final bool
setImageMatrixScale(useDefaultAnimation ? HIDE_ICON_SCALE : SPEC_HIDE_ICON_SCALE);
}
-
AnimatorSet set;
if (showMotionSpec != null) {
- set =
- createAnimator(
- showMotionSpec,
- SHOW_OPACITY,
- SHOW_SCALE,
- SHOW_ICON_SCALE);
+ set = createAnimator(showMotionSpec, SHOW_OPACITY, SHOW_SCALE, SHOW_ICON_SCALE);
} else {
set =
createDefaultAnimator(
@@ -623,15 +654,18 @@ public Matrix evaluate(
/**
* Create an AnimatorSet when there is no motion spec specified for a show or hide animation.
*
- * The created animation uses theme-based values for duration and easing. The benefits of this
+ * The created animation uses theme-based values for duration and easing. The benefits of this
* default animator is that it is able to use a single, value-driven animator to make property
* updates to the FAB. These property updates share a duration and follow the same easing curve
* and are able to interpolate values on their own to change the progress range over which they
* are changed.
*/
private AnimatorSet createDefaultAnimator(
- final float targetOpacity, final float targetScale, final float targetIconScale,
- final int duration, final int interpolator) {
+ final float targetOpacity,
+ final float targetScale,
+ final float targetIconScale,
+ final int duration,
+ final int interpolator) {
AnimatorSet set = new AnimatorSet();
List animators = new ArrayList<>();
ValueAnimator animator = ValueAnimator.ofFloat(0F, 1F);
@@ -641,20 +675,16 @@ private AnimatorSet createDefaultAnimator(
final float startImageMatrixScale = imageMatrixScale;
final Matrix matrix = new Matrix(tmpMatrix);
animator.addUpdateListener(
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float progress = (float) animation.getAnimatedValue();
- // Animate the opacity over the first 20% of the animation
- view.setAlpha(AnimationUtils.lerp(startAlpha, targetOpacity, 0F, 0.2F, progress));
- view.setScaleX(AnimationUtils.lerp(startScaleX, targetScale, progress));
- view.setScaleY(AnimationUtils.lerp(startScaleY, targetScale, progress));
- imageMatrixScale =
- AnimationUtils.lerp(startImageMatrixScale, targetIconScale, progress);
- calculateImageMatrixFromScale(
- AnimationUtils.lerp(startImageMatrixScale, targetIconScale, progress), matrix);
- view.setImageMatrix(matrix);
- }
+ animation -> {
+ float progress = (float) animation.getAnimatedValue();
+ // Animate the opacity over the first 20% of the animation
+ view.setAlpha(AnimationUtils.lerp(startAlpha, targetOpacity, 0F, 0.2F, progress));
+ view.setScaleX(AnimationUtils.lerp(startScaleX, targetScale, progress));
+ view.setScaleY(AnimationUtils.lerp(startScaleY, targetScale, progress));
+ imageMatrixScale = AnimationUtils.lerp(startImageMatrixScale, targetIconScale, progress);
+ calculateImageMatrixFromScale(
+ AnimationUtils.lerp(startImageMatrixScale, targetIconScale, progress), matrix);
+ view.setImageMatrix(matrix);
});
animators.add(animator);
AnimatorSetCompat.playTogether(set, animators);
@@ -667,9 +697,7 @@ public void onAnimationUpdate(ValueAnimator animation) {
.getInteger(R.integer.material_motion_duration_long_1)));
set.setInterpolator(
MotionUtils.resolveThemeInterpolator(
- view.getContext(),
- interpolator,
- AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR));
+ view.getContext(), interpolator, AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR));
return set;
}
@@ -682,14 +710,16 @@ private void workAroundOreoBug(final ObjectAnimator animator) {
return;
}
- animator.setEvaluator(new TypeEvaluator() {
- FloatEvaluator floatEvaluator = new FloatEvaluator();
- @Override
- public Float evaluate(float fraction, Float startValue, Float endValue) {
- float evaluated = floatEvaluator.evaluate(fraction, startValue, endValue);
- return evaluated < 0.1f ? 0.0f : evaluated;
- }
- });
+ animator.setEvaluator(
+ new TypeEvaluator() {
+ final FloatEvaluator floatEvaluator = new FloatEvaluator();
+
+ @Override
+ public Float evaluate(float fraction, Float startValue, Float endValue) {
+ float evaluated = floatEvaluator.evaluate(fraction, startValue, endValue);
+ return evaluated < 0.1f ? 0.0f : evaluated;
+ }
+ });
}
void addTransformationCallback(@NonNull InternalTransformationCallback listener) {
@@ -730,7 +760,7 @@ final Drawable getContentBackground() {
}
void onCompatShadowChanged() {
- // Ignore pre-v21
+ updatePadding();
}
final void updatePadding() {
@@ -741,25 +771,33 @@ final void updatePadding() {
}
void getPadding(@NonNull Rect rect) {
- final int touchTargetPadding = getTouchTargetPadding();
- final float maxShadowSize = shadowPaddingEnabled ? (getElevation() + pressedTranslationZ) : 0;
- final int hPadding = Math.max(touchTargetPadding, (int) Math.ceil(maxShadowSize));
- final int vPadding = Math.max(
- touchTargetPadding, (int) Math.ceil(maxShadowSize * SHADOW_MULTIPLIER));
- rect.set(hPadding, vPadding, hPadding, vPadding);
+ if (shadowViewDelegate.isCompatPaddingEnabled()) {
+ final int touchTargetPadding = getTouchTargetPadding();
+ final float maxShadowSize = shadowPaddingEnabled ? (getElevation() + pressedTranslationZ) : 0;
+ final int hPadding = max(touchTargetPadding, (int) Math.ceil(maxShadowSize));
+ final int vPadding =
+ max(touchTargetPadding, (int) Math.ceil(maxShadowSize * SHADOW_MULTIPLIER));
+ rect.set(hPadding, vPadding, hPadding, vPadding);
+ } else if (ignoreExpandBoundsForA11y()) {
+ int minPadding = (minTouchTargetSize - view.getSizeDimension()) / 2;
+ rect.set(minPadding, minPadding, minPadding, minPadding);
+ } else {
+ rect.set(0, 0, 0, 0);
+ }
}
int getTouchTargetPadding() {
return ensureMinTouchTargetSize
- ? Math.max((minTouchTargetSize - view.getSizeDimension()) / 2, 0)
+ ? max((minTouchTargetSize - view.getSizeDimension()) / 2, 0)
: 0;
}
void onPaddingUpdated(@NonNull Rect padding) {
Preconditions.checkNotNull(contentBackground, "Didn't initialize content background");
if (shouldAddPadding()) {
- InsetDrawable insetDrawable = new InsetDrawable(
- contentBackground, padding.left, padding.top, padding.right, padding.bottom);
+ InsetDrawable insetDrawable =
+ new InsetDrawable(
+ contentBackground, padding.left, padding.top, padding.right, padding.bottom);
shadowViewDelegate.setBackgroundDrawable(insetDrawable);
} else {
shadowViewDelegate.setBackgroundDrawable(contentBackground);
@@ -767,17 +805,13 @@ void onPaddingUpdated(@NonNull Rect padding) {
}
boolean shouldAddPadding() {
- return true;
+ return shadowViewDelegate.isCompatPaddingEnabled() || ignoreExpandBoundsForA11y();
}
void onAttachedToWindow() {
if (shapeDrawable != null) {
MaterialShapeUtils.setParentAbsoluteElevation(view, shapeDrawable);
}
-
- if (requirePreDrawListener()) {
- view.getViewTreeObserver().addOnPreDrawListener(getOrCreatePreDrawListener());
- }
}
void onDetachedFromWindow() {
@@ -788,37 +822,23 @@ void onDetachedFromWindow() {
}
}
- boolean requirePreDrawListener() {
- return true;
- }
-
- void onPreDraw() {
- final float rotation = view.getRotation();
- if (this.rotation != rotation) {
- this.rotation = rotation;
- updateFromViewRotation();
- }
- }
-
@NonNull
- private ViewTreeObserver.OnPreDrawListener getOrCreatePreDrawListener() {
- if (preDrawListener == null) {
- preDrawListener =
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- FloatingActionButtonImpl.this.onPreDraw();
- return true;
- }
- };
- }
-
- return preDrawListener;
+ BorderDrawable createBorderDrawable(int borderWidth, ColorStateList backgroundTint) {
+ final Context context = view.getContext();
+ BorderDrawable borderDrawable = new BorderDrawable(checkNotNull(shapeAppearance));
+ borderDrawable.setGradientColors(
+ ContextCompat.getColor(context, R.color.design_fab_stroke_top_outer_color),
+ ContextCompat.getColor(context, R.color.design_fab_stroke_top_inner_color),
+ ContextCompat.getColor(context, R.color.design_fab_stroke_end_inner_color),
+ ContextCompat.getColor(context, R.color.design_fab_stroke_end_outer_color));
+ borderDrawable.setBorderWidth(borderWidth);
+ borderDrawable.setBorderTint(backgroundTint);
+ return borderDrawable;
}
MaterialShapeDrawable createShapeDrawable() {
ShapeAppearanceModel shapeAppearance = checkNotNull(this.shapeAppearance);
- return new MaterialShapeDrawable(shapeAppearance);
+ return new AlwaysStatefulMaterialShapeDrawable(shapeAppearance);
}
boolean isOrWillBeShown() {
@@ -842,91 +862,35 @@ boolean isOrWillBeHidden() {
}
@NonNull
- private ValueAnimator createElevationAnimator(@NonNull ShadowAnimatorImpl impl) {
- final ValueAnimator animator = new ValueAnimator();
- animator.setInterpolator(ELEVATION_ANIM_INTERPOLATOR);
- animator.setDuration(ELEVATION_ANIM_DURATION);
- animator.addListener(impl);
- animator.addUpdateListener(impl);
- animator.setFloatValues(0, 1);
- return animator;
- }
-
- private abstract class ShadowAnimatorImpl extends AnimatorListenerAdapter
- implements ValueAnimator.AnimatorUpdateListener {
-
- private boolean validValues;
- private float shadowSizeStart;
- private float shadowSizeEnd;
-
- @Override
- public void onAnimationUpdate(@NonNull ValueAnimator animator) {
- if (!validValues) {
- shadowSizeStart = shapeDrawable == null ? 0 : shapeDrawable.getElevation();
- shadowSizeEnd = getTargetShadowSize();
- validValues = true;
- }
-
- updateShapeElevation(
- (int)
- (shadowSizeStart
- + ((shadowSizeEnd - shadowSizeStart) * animator.getAnimatedFraction())));
- }
-
- @Override
- public void onAnimationEnd(Animator animator) {
- updateShapeElevation((int) shadowSizeEnd);
- validValues = false;
- }
-
- /** Returns the shadow size we want to animate to. */
- protected abstract float getTargetShadowSize();
- }
-
- private class ResetElevationAnimation extends ShadowAnimatorImpl {
- ResetElevationAnimation() {}
-
- @Override
- protected float getTargetShadowSize() {
- return elevation;
- }
+ private Animator createElevationAnimator(float elevation, float translationZ) {
+ AnimatorSet set = new AnimatorSet();
+ set.play(ObjectAnimator.ofFloat(view, "elevation", elevation).setDuration(0))
+ .with(
+ ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, translationZ)
+ .setDuration(ELEVATION_ANIM_DURATION));
+ set.setInterpolator(ELEVATION_ANIM_INTERPOLATOR);
+ return set;
}
- private class ElevateToHoveredFocusedTranslationZAnimation extends ShadowAnimatorImpl {
- ElevateToHoveredFocusedTranslationZAnimation() {}
-
- @Override
- protected float getTargetShadowSize() {
- return elevation + hoveredFocusedTranslationZ;
- }
+ private boolean shouldAnimateVisibilityChange() {
+ return view.isLaidOut() && !view.isInEditMode();
}
- private class ElevateToPressedTranslationZAnimation extends ShadowAnimatorImpl {
- ElevateToPressedTranslationZAnimation() {}
+ /**
+ * LayerDrawable on L+ caches its isStateful() state and doesn't refresh it, meaning that if we
+ * apply a tint to one of its children, the parent doesn't become stateful and the tint doesn't
+ * work for state changes. We workaround it by saying that we are always stateful. If we don't
+ * have a stateful tint, the change is ignored anyway.
+ */
+ static class AlwaysStatefulMaterialShapeDrawable extends MaterialShapeDrawable {
- @Override
- protected float getTargetShadowSize() {
- return elevation + pressedTranslationZ;
+ AlwaysStatefulMaterialShapeDrawable(ShapeAppearanceModel shapeAppearanceModel) {
+ super(shapeAppearanceModel);
}
- }
-
- private class DisabledElevationAnimation extends ShadowAnimatorImpl {
- DisabledElevationAnimation() {}
@Override
- protected float getTargetShadowSize() {
- return 0f;
- }
- }
-
- private boolean shouldAnimateVisibilityChange() {
- return view.isLaidOut() && !view.isInEditMode();
- }
-
- void updateFromViewRotation() {
- // Offset any View rotation
- if (shapeDrawable != null) {
- shapeDrawable.setShadowCompatRotation((int) rotation);
+ public boolean isStateful() {
+ return true;
}
}
}
diff --git a/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButtonImplLollipop.java b/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButtonImplLollipop.java
deleted file mode 100644
index 67753fcc097..00000000000
--- a/lib/java/com/google/android/material/floatingactionbutton/FloatingActionButtonImplLollipop.java
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * Copyright (C) 2015 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.floatingactionbutton;
-
-import com.google.android.material.R;
-
-import static androidx.core.util.Preconditions.checkNotNull;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.StateListAnimator;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.RippleDrawable;
-import android.os.Build;
-import android.os.Build.VERSION_CODES;
-import android.view.View;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
-import com.google.android.material.ripple.RippleUtils;
-import com.google.android.material.shadow.ShadowViewDelegate;
-import com.google.android.material.shape.MaterialShapeDrawable;
-import com.google.android.material.shape.ShapeAppearanceModel;
-import java.util.ArrayList;
-import java.util.List;
-
-class FloatingActionButtonImplLollipop extends FloatingActionButtonImpl {
- @Nullable private StateListAnimator stateListAnimator;
-
- FloatingActionButtonImplLollipop(
- FloatingActionButton view, ShadowViewDelegate shadowViewDelegate) {
- super(view, shadowViewDelegate);
- }
-
- @Override
- void initializeBackgroundDrawable(
- ColorStateList backgroundTint,
- @Nullable PorterDuff.Mode backgroundTintMode,
- ColorStateList rippleColor,
- int borderWidth) {
- // Now we need to tint the shape background with the tint
- shapeDrawable = createShapeDrawable();
- shapeDrawable.setTintList(backgroundTint);
- if (backgroundTintMode != null) {
- shapeDrawable.setTintMode(backgroundTintMode);
- }
- shapeDrawable.initializeElevationOverlay(view.getContext());
-
- final Drawable rippleContent;
- if (borderWidth > 0) {
- borderDrawable = createBorderDrawable(borderWidth, backgroundTint);
- rippleContent = new LayerDrawable(
- new Drawable[]{checkNotNull(borderDrawable), checkNotNull(shapeDrawable)});
- } else {
- borderDrawable = null;
- rippleContent = shapeDrawable;
- }
-
- rippleDrawable =
- new RippleDrawable(
- RippleUtils.sanitizeRippleDrawableColor(rippleColor), rippleContent, null);
-
- contentBackground = rippleDrawable;
- }
-
- @Override
- void setRippleColor(@Nullable ColorStateList rippleColor) {
- if (rippleDrawable instanceof RippleDrawable) {
- ((RippleDrawable) rippleDrawable)
- .setColor(RippleUtils.sanitizeRippleDrawableColor(rippleColor));
- } else {
- super.setRippleColor(rippleColor);
- }
- }
-
- @Override
- void onElevationsChanged(
- final float elevation,
- final float hoveredFocusedTranslationZ,
- final float pressedTranslationZ) {
-
- if (Build.VERSION.SDK_INT == VERSION_CODES.LOLLIPOP) {
- // Animations produce NPE in version 21. Bluntly set the values instead in
- // #onDrawableStateChanged (matching the logic in the animations below).
- view.refreshDrawableState();
- } else if (view.getStateListAnimator() == stateListAnimator) {
- // FAB is using the default StateListAnimator created here. Updates it with the new elevation.
- stateListAnimator = createDefaultStateListAnimator(
- elevation, hoveredFocusedTranslationZ, pressedTranslationZ);
- view.setStateListAnimator(stateListAnimator);
- }
-
- if (shouldAddPadding()) {
- updatePadding();
- }
- }
-
- @NonNull
- private StateListAnimator createDefaultStateListAnimator(
- final float elevation,
- final float hoveredFocusedTranslationZ,
- final float pressedTranslationZ) {
- StateListAnimator stateListAnimator = new StateListAnimator();
-
- // Animate elevation and translationZ to our values when pressed, focused, and hovered
- stateListAnimator.addState(
- PRESSED_ENABLED_STATE_SET, createElevationAnimator(elevation, pressedTranslationZ));
- stateListAnimator.addState(
- HOVERED_FOCUSED_ENABLED_STATE_SET,
- createElevationAnimator(elevation, hoveredFocusedTranslationZ));
- stateListAnimator.addState(
- FOCUSED_ENABLED_STATE_SET,
- createElevationAnimator(elevation, hoveredFocusedTranslationZ));
- stateListAnimator.addState(
- HOVERED_ENABLED_STATE_SET,
- createElevationAnimator(elevation, hoveredFocusedTranslationZ));
-
- // Animate translationZ to 0 if not pressed, focused, or hovered
- AnimatorSet set = new AnimatorSet();
- List animators = new ArrayList<>();
- animators.add(ObjectAnimator.ofFloat(view, "elevation", elevation).setDuration(0));
- if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP_MR1
- && Build.VERSION.SDK_INT <= VERSION_CODES.N) {
- // This is a no-op animation which exists here only for introducing the duration
- // because setting the delay (on the next animation) via "setDelay" or "after"
- // can trigger a NPE between android versions 22 and 24 (due to a framework
- // bug). The issue has been fixed in version 25.
- animators.add(
- ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, view.getTranslationZ())
- .setDuration(ELEVATION_ANIM_DELAY));
- }
- animators.add(
- ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, 0f)
- .setDuration(ELEVATION_ANIM_DURATION));
- set.playSequentially(animators.toArray(new Animator[0]));
- set.setInterpolator(ELEVATION_ANIM_INTERPOLATOR);
- stateListAnimator.addState(ENABLED_STATE_SET, set);
-
- // Animate everything to 0 when disabled
- stateListAnimator.addState(EMPTY_STATE_SET, createElevationAnimator(0f, 0f));
-
- return stateListAnimator;
- }
-
- @NonNull
- private Animator createElevationAnimator(float elevation, float translationZ) {
- AnimatorSet set = new AnimatorSet();
- set.play(ObjectAnimator.ofFloat(view, "elevation", elevation).setDuration(0))
- .with(
- ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, translationZ)
- .setDuration(ELEVATION_ANIM_DURATION));
- set.setInterpolator(ELEVATION_ANIM_INTERPOLATOR);
- return set;
- }
-
- @Override
- public float getElevation() {
- return view.getElevation();
- }
-
- @Override
- void onCompatShadowChanged() {
- updatePadding();
- }
-
- @Override
- boolean shouldAddPadding() {
- return shadowViewDelegate.isCompatPaddingEnabled() || !shouldExpandBoundsForA11y();
- }
-
- @Override
- void onDrawableStateChanged(int[] state) {
- if (Build.VERSION.SDK_INT == VERSION_CODES.LOLLIPOP) {
- if (view.isEnabled()) {
- view.setElevation(elevation);
- if (view.isPressed()) {
- view.setTranslationZ(pressedTranslationZ);
- } else if (view.isFocused() || view.isHovered()) {
- view.setTranslationZ(hoveredFocusedTranslationZ);
- } else {
- view.setTranslationZ(0);
- }
- } else {
- view.setElevation(0);
- view.setTranslationZ(0);
- }
- }
- }
-
- @Override
- void jumpDrawableToCurrentState() {
- // no-op
- }
-
- @Override
- void updateFromViewRotation() {
- // no-op
- }
-
- @Override
- boolean requirePreDrawListener() {
- return false;
- }
-
- @NonNull
- BorderDrawable createBorderDrawable(int borderWidth, ColorStateList backgroundTint) {
- final Context context = view.getContext();
- BorderDrawable borderDrawable = new BorderDrawable(checkNotNull(shapeAppearance));
- borderDrawable.setGradientColors(
- ContextCompat.getColor(context, R.color.design_fab_stroke_top_outer_color),
- ContextCompat.getColor(context, R.color.design_fab_stroke_top_inner_color),
- ContextCompat.getColor(context, R.color.design_fab_stroke_end_inner_color),
- ContextCompat.getColor(context, R.color.design_fab_stroke_end_outer_color));
- borderDrawable.setBorderWidth(borderWidth);
- borderDrawable.setBorderTint(backgroundTint);
- return borderDrawable;
- }
-
- @NonNull
- @Override
- MaterialShapeDrawable createShapeDrawable() {
- ShapeAppearanceModel shapeAppearance = checkNotNull(this.shapeAppearance);
- return new AlwaysStatefulMaterialShapeDrawable(shapeAppearance);
- }
-
- @Override
- void getPadding(@NonNull Rect rect) {
- if (shadowViewDelegate.isCompatPaddingEnabled()) {
- super.getPadding(rect);
- } else if (!shouldExpandBoundsForA11y()) {
- int minPadding = (minTouchTargetSize - view.getSizeDimension()) / 2;
- rect.set(minPadding, minPadding, minPadding, minPadding);
- } else {
- rect.set(0, 0, 0, 0);
- }
- }
-
- /**
- * LayerDrawable on L+ caches its isStateful() state and doesn't refresh it, meaning that if we
- * apply a tint to one of its children, the parent doesn't become stateful and the tint doesn't
- * work for state changes. We workaround it by saying that we are always stateful. If we don't
- * have a stateful tint, the change is ignored anyway.
- */
- static class AlwaysStatefulMaterialShapeDrawable extends MaterialShapeDrawable {
-
- AlwaysStatefulMaterialShapeDrawable(ShapeAppearanceModel shapeAppearanceModel) {
- super(shapeAppearanceModel);
- }
-
- @Override
- public boolean isStateful() {
- return true;
- }
- }
-}
diff --git a/lib/java/com/google/android/material/floatingactionbutton/res-public/values/public.xml b/lib/java/com/google/android/material/floatingactionbutton/res-public/values/public.xml
index 7eea61e0106..c9de0ed485b 100644
--- a/lib/java/com/google/android/material/floatingactionbutton/res-public/values/public.xml
+++ b/lib/java/com/google/android/material/floatingactionbutton/res-public/values/public.xml
@@ -38,12 +38,16 @@
+
+
+
+
diff --git a/lib/java/com/google/android/material/floatingactionbutton/res/animator/m3_extended_fab_state_list_animator.xml b/lib/java/com/google/android/material/floatingactionbutton/res/animator/m3_extended_fab_state_list_animator.xml
index 872cba33395..38a53f9ffdd 100644
--- a/lib/java/com/google/android/material/floatingactionbutton/res/animator/m3_extended_fab_state_list_animator.xml
+++ b/lib/java/com/google/android/material/floatingactionbutton/res/animator/m3_extended_fab_state_list_animator.xml
@@ -26,12 +26,12 @@
@@ -44,12 +44,12 @@
@@ -62,12 +62,12 @@
@@ -79,12 +79,12 @@
android:duration="@integer/mtrl_btn_anim_duration_ms"
android:propertyName="translationZ"
android:startDelay="@integer/mtrl_btn_anim_delay_ms"
- android:valueTo="@dimen/mtrl_extended_fab_translation_z_base"
+ android:valueTo="@dimen/m3_extended_fab_translation_z_base"
android:valueType="floatType"/>
@@ -95,12 +95,12 @@
diff --git a/lib/java/com/google/android/material/floatingactionbutton/res/animator/m3_fab_state_list_animator.xml b/lib/java/com/google/android/material/floatingactionbutton/res/animator/m3_fab_state_list_animator.xml
new file mode 100644
index 00000000000..3cddb799675
--- /dev/null
+++ b/lib/java/com/google/android/material/floatingactionbutton/res/animator/m3_fab_state_list_animator.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/floatingactionbutton/res/color/m3_efab_ripple_color_selector.xml b/lib/java/com/google/android/material/floatingactionbutton/res/color/m3_efab_ripple_color_selector.xml
index b319c45a7e9..1bbc2046c58 100644
--- a/lib/java/com/google/android/material/floatingactionbutton/res/color/m3_efab_ripple_color_selector.xml
+++ b/lib/java/com/google/android/material/floatingactionbutton/res/color/m3_efab_ripple_color_selector.xml
@@ -15,8 +15,11 @@
~ limitations under the License.
-->
-
-
-
+
+
+
diff --git a/lib/java/com/google/android/material/floatingactionbutton/res/color/m3_fab_ripple_color_selector.xml b/lib/java/com/google/android/material/floatingactionbutton/res/color/m3_fab_ripple_color_selector.xml
index 6906fb823f8..3a2bc09ce32 100644
--- a/lib/java/com/google/android/material/floatingactionbutton/res/color/m3_fab_ripple_color_selector.xml
+++ b/lib/java/com/google/android/material/floatingactionbutton/res/color/m3_fab_ripple_color_selector.xml
@@ -15,8 +15,11 @@
~ limitations under the License.
-->
-
-
-
+
+
+
diff --git a/lib/java/com/google/android/material/floatingactionbutton/res/values/attrs.xml b/lib/java/com/google/android/material/floatingactionbutton/res/values/attrs.xml
index b83a88bb590..d4b1ee11ea6 100644
--- a/lib/java/com/google/android/material/floatingactionbutton/res/values/attrs.xml
+++ b/lib/java/com/google/android/material/floatingactionbutton/res/values/attrs.xml
@@ -132,6 +132,9 @@
+
+
+
@@ -161,4 +164,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/floatingactionbutton/res/values/dimens.xml b/lib/java/com/google/android/material/floatingactionbutton/res/values/dimens.xml
index 1fa9ba065c3..366673f0e21 100644
--- a/lib/java/com/google/android/material/floatingactionbutton/res/values/dimens.xml
+++ b/lib/java/com/google/android/material/floatingactionbutton/res/values/dimens.xml
@@ -26,6 +26,7 @@
6dp
2dp
+
6dp
48dp
@@ -48,8 +49,14 @@
0dp
30%
- 2dp
- 6dp
+ 0dp
+ 0dp
+
+ 2dp
+
+ 0dp
+ 0dp
+ 0dp
12dp
20dp
@@ -57,6 +64,14 @@
16dp
8dp
56dp
+ 0dp
+ 0dp
+
+ 0dp
+ 0dp
+ 0dp
+
+ 2dp
40dp
24dp
diff --git a/lib/java/com/google/android/material/floatingactionbutton/res/values/efab_tokens.xml b/lib/java/com/google/android/material/floatingactionbutton/res/values/efab_tokens.xml
index e6e318f2c0a..59b662b7075 100644
--- a/lib/java/com/google/android/material/floatingactionbutton/res/values/efab_tokens.xml
+++ b/lib/java/com/google/android/material/floatingactionbutton/res/values/efab_tokens.xml
@@ -15,46 +15,65 @@
~ limitations under the License.
-->
-
-
+
-
-
- ?attr/colorPrimaryContainer
- ?attr/shapeAppearanceCornerLarge
- 56dp
- @dimen/m3_sys_elevation_level3
-
- ?attr/textAppearanceLabelLarge
-
- 24dp
- ?attr/colorOnPrimaryContainer
-
- @dimen/m3_sys_elevation_level4
-
- @dimen/m3_sys_state_hover_state_layer_opacity
-
- @dimen/m3_sys_elevation_level3
-
- @dimen/m3_sys_state_focus_state_layer_opacity
-
- @dimen/m3_sys_elevation_level3
-
- @dimen/m3_sys_state_pressed_state_layer_opacity
+
+ 56dp
+ ?attr/textAppearanceTitleMedium
+ 24dp
+ ?attr/shapeAppearanceCornerLarge
+ 16dp
+ 8dp
+ 16dp
-
-
- ?attr/colorSecondaryContainer
-
- ?attr/colorOnSecondaryContainer
+
+ 80dp
+ ?attr/textAppearanceTitleLarge
+ 28dp
+ ?attr/shapeAppearanceCornerLargeIncreased
+ 26dp
+ 12dp
+ 26dp
-
-
- ?attr/colorTertiaryContainer
-
- ?attr/colorOnTertiaryContainer
+
+ 96dp
+ ?attr/textAppearanceHeadlineSmall
+ 36dp
+ ?attr/shapeAppearanceCornerExtraLarge
+ 28dp
+ 16dp
+ 28dp
+
+
+
+ ?attr/colorPrimaryContainer
+ @dimen/m3_sys_elevation_level3
+ ?attr/colorOnPrimaryContainer
+
+ @dimen/m3_sys_elevation_level4
+ @dimen/m3_sys_state_hover_state_layer_opacity
+
+ @dimen/m3_sys_elevation_level3
+ @dimen/m3_sys_state_focus_state_layer_opacity
+
+ @dimen/m3_sys_elevation_level3
+ @dimen/m3_sys_state_pressed_state_layer_opacity
+
+
+
+ ?attr/colorSecondaryContainer
+
+ ?attr/colorOnSecondaryContainer
+
+
+
+ ?attr/colorTertiaryContainer
+ ?attr/colorOnTertiaryContainer
diff --git a/lib/java/com/google/android/material/floatingactionbutton/res/values/fab_tokens.xml b/lib/java/com/google/android/material/floatingactionbutton/res/values/fab_tokens.xml
index e64c03671ea..05410b5c68f 100644
--- a/lib/java/com/google/android/material/floatingactionbutton/res/values/fab_tokens.xml
+++ b/lib/java/com/google/android/material/floatingactionbutton/res/values/fab_tokens.xml
@@ -15,56 +15,70 @@
~ limitations under the License.
-->
-
-
+
-
-
- ?attr/colorPrimaryContainer
- ?attr/shapeAppearanceCornerLarge
- 56dp
- @dimen/m3_sys_elevation_level3
-
- 24dp
- ?attr/colorOnPrimaryContainer
-
- @dimen/m3_sys_elevation_level4
-
- @dimen/m3_sys_state_hover_state_layer_opacity
-
- @dimen/m3_sys_state_focus_state_layer_opacity
-
- @dimen/m3_sys_elevation_level3
-
- @dimen/m3_sys_state_pressed_state_layer_opacity
+
+ 56dp
+ 24dp
+ ?attr/shapeAppearanceCornerLarge
-
-
- ?attr/shapeAppearanceCornerMedium
- 40dp
-
- 24dp
+
+ 40dp
+ 24dp
+ ?attr/shapeAppearanceCornerMedium
-
-
- ?attr/shapeAppearanceCornerExtraLarge
- 96dp
-
- 36dp
+
+ 80dp
+ 28dp
+ ?attr/shapeAppearanceCornerLargeIncreased
+
+
+ 96dp
+ 36dp
+ ?attr/shapeAppearanceCornerExtraLarge
+
+
+
+ ?attr/colorPrimaryContainer
+ @dimen/m3_sys_elevation_level3
+ ?attr/colorOnPrimaryContainer
+
+ @dimen/m3_sys_elevation_level4
+ @dimen/m3_sys_state_hover_state_layer_opacity
+
+ @dimen/m3_sys_elevation_level3
+ @dimen/m3_sys_state_focus_state_layer_opacity
+
+ @dimen/m3_sys_elevation_level3
+ @dimen/m3_sys_state_pressed_state_layer_opacity
+
+
+
+ ?attr/colorSecondaryContainer
+ ?attr/colorOnSecondaryContainer
+
+
+
+ ?attr/colorTertiaryContainer
+ ?attr/colorOnTertiaryContainer
+
+
+
+ ?attr/colorPrimary
+ ?attr/colorOnPrimary
-
- ?attr/colorSecondaryContainer
-
- ?attr/colorOnSecondaryContainer
+
+ ?attr/colorSecondary
+ ?attr/colorOnSecondary
-
- ?attr/colorTertiaryContainer
-
- ?attr/colorOnTertiaryContainer
+
+ ?attr/colorTertiary
+ ?attr/colorOnTertiary
diff --git a/lib/java/com/google/android/material/floatingactionbutton/res/values/styles.xml b/lib/java/com/google/android/material/floatingactionbutton/res/values/styles.xml
index 9f4762692c1..391d73ee0ca 100644
--- a/lib/java/com/google/android/material/floatingactionbutton/res/values/styles.xml
+++ b/lib/java/com/google/android/material/floatingactionbutton/res/values/styles.xml
@@ -16,93 +16,74 @@
-->
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/floatingtoolbar/FloatingToolbarLayout.java b/lib/java/com/google/android/material/floatingtoolbar/FloatingToolbarLayout.java
index dbd0f8f8112..cc659a76fdd 100644
--- a/lib/java/com/google/android/material/floatingtoolbar/FloatingToolbarLayout.java
+++ b/lib/java/com/google/android/material/floatingtoolbar/FloatingToolbarLayout.java
@@ -22,16 +22,21 @@
import android.content.Context;
import android.content.res.ColorStateList;
+import android.graphics.Rect;
import androidx.appcompat.widget.TintTypedArray;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
-import androidx.coordinatorlayout.widget.CoordinatorLayout.AttachedBehavior;
-import com.google.android.material.behavior.HideViewOnScrollBehavior;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
import com.google.android.material.internal.ThemeEnforcement;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.shape.ShapeAppearanceModel;
@@ -46,9 +51,20 @@
* provides styling for the provided child to give a uniform "floating toolbar" appearance to the
* {@link android.view.ViewGroup}.
*/
-public class FloatingToolbarLayout extends FrameLayout implements AttachedBehavior {
- private static final int DEF_STYLE_RES = R.style.Widget_Material3_FloatingToolbar_Horizontal;
- @Nullable private Behavior behavior;
+public class FloatingToolbarLayout extends FrameLayout {
+
+ private static final String TAG = FloatingToolbarLayout.class.getSimpleName();
+ private static final int DEF_STYLE_RES = R.style.Widget_Material3_FloatingToolbar;
+
+ private boolean marginLeftSystemWindowInsets;
+ private boolean marginTopSystemWindowInsets;
+ private boolean marginRightSystemWindowInsets;
+ private boolean marginBottomSystemWindowInsets;
+ private Rect originalMargins;
+ private int bottomMarginWindowInset;
+ private int topMarginWindowInset;
+ private int leftMarginWindowInset;
+ private int rightMarginWindowInset;
public FloatingToolbarLayout(@NonNull Context context) {
this(context, null);
@@ -91,22 +107,90 @@ public FloatingToolbarLayout(
setBackground(materialShapeDrawable);
}
+ // Reading out if we are handling inset margins, so we can apply it to the content.
+ marginLeftSystemWindowInsets = attributes.getBoolean(R.styleable.FloatingToolbar_marginLeftSystemWindowInsets, true);
+ // Top-aligned floating toolbars are not recommended, so a top inset margin is turned off by default
+ marginTopSystemWindowInsets = attributes.getBoolean(R.styleable.FloatingToolbar_marginTopSystemWindowInsets, false);
+ marginRightSystemWindowInsets = attributes.getBoolean(R.styleable.FloatingToolbar_marginRightSystemWindowInsets, true);
+ marginBottomSystemWindowInsets = attributes.getBoolean(R.styleable.FloatingToolbar_marginBottomSystemWindowInsets, true);
+
+ ViewCompat.setOnApplyWindowInsetsListener(
+ this,
+ new androidx.core.view.OnApplyWindowInsetsListener() {
+ @NonNull
+ @Override
+ public WindowInsetsCompat onApplyWindowInsets(
+ @NonNull View v, @NonNull WindowInsetsCompat insets) {
+ if (!marginLeftSystemWindowInsets
+ && !marginRightSystemWindowInsets
+ && !marginTopSystemWindowInsets
+ && !marginBottomSystemWindowInsets) {
+ return insets;
+ }
+ Insets systemBarInsets =
+ insets.getInsets(
+ WindowInsetsCompat.Type.systemBars()
+ | WindowInsetsCompat.Type.displayCutout()
+ | WindowInsetsCompat.Type.ime());
+ bottomMarginWindowInset = systemBarInsets.bottom;
+ topMarginWindowInset = systemBarInsets.top;
+ rightMarginWindowInset = systemBarInsets.right;
+ leftMarginWindowInset = systemBarInsets.left;
+
+ updateMargins();
+
+ return insets;
+ }
+ });
+
attributes.recycle();
}
- @Override
- @NonNull
- public Behavior getBehavior() {
- if (behavior == null) {
- behavior = new Behavior();
+ private void updateMargins() {
+ ViewGroup.LayoutParams lp = getLayoutParams();
+ if (originalMargins == null) {
+ Log.w(TAG, "Unable to update margins because original view margins are not set");
+ return;
+ }
+
+ int newLeftMargin =
+ originalMargins.left + (marginLeftSystemWindowInsets ? leftMarginWindowInset : 0);
+ int newRightMargin =
+ originalMargins.right + (marginRightSystemWindowInsets ? rightMarginWindowInset : 0);
+ int newTopMargin =
+ originalMargins.top + (marginTopSystemWindowInsets ? topMarginWindowInset : 0);
+ int newBottomMargin =
+ originalMargins.bottom + (marginBottomSystemWindowInsets ? bottomMarginWindowInset : 0);
+
+ MarginLayoutParams marginLp = (MarginLayoutParams) lp;
+ boolean marginChanged =
+ marginLp.bottomMargin != newBottomMargin
+ || marginLp.leftMargin != newLeftMargin
+ || marginLp.rightMargin != newRightMargin
+ || marginLp.topMargin != newTopMargin;
+ if (marginChanged) {
+ marginLp.bottomMargin = newBottomMargin;
+ marginLp.leftMargin = newLeftMargin;
+ marginLp.rightMargin = newRightMargin;
+ marginLp.topMargin = newTopMargin;
+ requestLayout();
}
- return behavior;
}
- /**
- * Behavior designed for use with {@link FloatingToolbarLayout} instances. Its main function is to
- * hide the {@link FloatingToolbarLayout} view when scrolling. Supports scrolling the floating
- * toolbar off of either the right, bottom or left edge of the screen.
- */
- public static class Behavior extends HideViewOnScrollBehavior {}
+ @Override
+ public void setLayoutParams(ViewGroup.LayoutParams params) {
+ super.setLayoutParams(params);
+ if (params instanceof MarginLayoutParams) {
+ MarginLayoutParams marginParams = (MarginLayoutParams) params;
+ originalMargins =
+ new Rect(
+ marginParams.leftMargin,
+ marginParams.topMargin,
+ marginParams.rightMargin,
+ marginParams.bottomMargin);
+ updateMargins();
+ } else {
+ originalMargins = null;
+ }
+ }
}
diff --git a/lib/java/com/google/android/material/floatingtoolbar/res-public/values/public.xml b/lib/java/com/google/android/material/floatingtoolbar/res-public/values/public.xml
index ba1dd57bcc0..ad45f8785c0 100644
--- a/lib/java/com/google/android/material/floatingtoolbar/res-public/values/public.xml
+++ b/lib/java/com/google/android/material/floatingtoolbar/res-public/values/public.xml
@@ -16,8 +16,11 @@
-->
-
-
-
-
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/floatingtoolbar/res/color/m3_floating_toolbar_vibrant_icon_button_container_color_selector.xml b/lib/java/com/google/android/material/floatingtoolbar/res/color/m3_floating_toolbar_vibrant_icon_button_container_color_selector.xml
new file mode 100644
index 00000000000..67243d2c02d
--- /dev/null
+++ b/lib/java/com/google/android/material/floatingtoolbar/res/color/m3_floating_toolbar_vibrant_icon_button_container_color_selector.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/floatingtoolbar/res/color/m3_floating_toolbar_vibrant_icon_button_text_color_selector.xml b/lib/java/com/google/android/material/floatingtoolbar/res/color/m3_floating_toolbar_vibrant_icon_button_text_color_selector.xml
new file mode 100644
index 00000000000..9dc6a826511
--- /dev/null
+++ b/lib/java/com/google/android/material/floatingtoolbar/res/color/m3_floating_toolbar_vibrant_icon_button_text_color_selector.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/floatingtoolbar/res/values/attrs.xml b/lib/java/com/google/android/material/floatingtoolbar/res/values/attrs.xml
index 9bb6373d836..6dac766aa29 100644
--- a/lib/java/com/google/android/material/floatingtoolbar/res/values/attrs.xml
+++ b/lib/java/com/google/android/material/floatingtoolbar/res/values/attrs.xml
@@ -15,11 +15,22 @@
~ limitations under the License.
-->
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/floatingtoolbar/res/values/styles.xml b/lib/java/com/google/android/material/floatingtoolbar/res/values/styles.xml
index dede1bb41a7..c400d186aac 100644
--- a/lib/java/com/google/android/material/floatingtoolbar/res/values/styles.xml
+++ b/lib/java/com/google/android/material/floatingtoolbar/res/values/styles.xml
@@ -15,49 +15,96 @@
~ limitations under the License.
-->
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
diff --git a/lib/java/com/google/android/material/floatingtoolbar/res/values/tokens.xml b/lib/java/com/google/android/material/floatingtoolbar/res/values/tokens.xml
index 05a20d4264c..043e56c40d3 100644
--- a/lib/java/com/google/android/material/floatingtoolbar/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/floatingtoolbar/res/values/tokens.xml
@@ -15,20 +15,19 @@
~ limitations under the License.
-->
-
+
-
- ?attr/colorSurfaceContainer
- ?attr/colorPrimaryContainer
- 64dp
+ 64dp
+ 64dp
8dp
8dp
- 16dp
+
+ @dimen/m3_sys_elevation_level3
diff --git a/lib/java/com/google/android/material/internal/CollapsingTextHelper.java b/lib/java/com/google/android/material/internal/CollapsingTextHelper.java
index ee76c7a02e6..308539c4819 100644
--- a/lib/java/com/google/android/material/internal/CollapsingTextHelper.java
+++ b/lib/java/com/google/android/material/internal/CollapsingTextHelper.java
@@ -73,13 +73,15 @@ public final class CollapsingTextHelper {
private static final float FADE_MODE_THRESHOLD_FRACTION_RELATIVE = 0.5f;
private static final boolean DEBUG_DRAW = false;
- @NonNull private static final Paint DEBUG_DRAW_PAINT;
+ @Nullable private static final Paint DEBUG_DRAW_PAINT;
+
+ public static final int SEMITRANSPARENT_MAGENTA = 0x40FF00FF;
static {
DEBUG_DRAW_PAINT = DEBUG_DRAW ? new Paint() : null;
if (DEBUG_DRAW_PAINT != null) {
DEBUG_DRAW_PAINT.setAntiAlias(true);
- DEBUG_DRAW_PAINT.setColor(Color.MAGENTA);
+ DEBUG_DRAW_PAINT.setColor(SEMITRANSPARENT_MAGENTA);
}
}
@@ -92,7 +94,12 @@ public final class CollapsingTextHelper {
private int currentOffsetY;
@NonNull private final Rect expandedBounds;
+ // collapsedBounds are valid bounds that text can be drawn inside.
@NonNull private final Rect collapsedBounds;
+ // collapsedBoundsForPlacement are collapsed bounds that are used for calculating the placement
+ // of the collapsed text, but may not be valid bounds for text. If not set, collapsedBounds will
+ // be used instead for the placement calculations.
+ @Nullable private Rect collapsedBoundsForPlacement;
@NonNull private final RectF currentBounds;
private int expandedTextGravity = Gravity.CENTER_VERTICAL;
private int collapsedTextGravity = Gravity.CENTER_VERTICAL;
@@ -172,6 +179,7 @@ public final class CollapsingTextHelper {
@Nullable private StaticLayoutBuilderConfigurer staticLayoutBuilderConfigurer;
private int collapsedHeight = -1;
private int expandedHeight = -1;
+ private boolean alignBaselineAtBottom;
public CollapsingTextHelper(View view) {
this.view = view;
@@ -252,13 +260,20 @@ public void setExpandedLetterSpacing(float letterSpacing) {
}
}
- public void setExpandedBounds(int left, int top, int right, int bottom) {
- if (!rectEquals(expandedBounds, left, top, right, bottom)) {
+ public void setExpandedBounds(
+ int left, int top, int right, int bottom, boolean alignBaselineAtBottom) {
+ if (!rectEquals(expandedBounds, left, top, right, bottom)
+ || alignBaselineAtBottom != this.alignBaselineAtBottom) {
expandedBounds.set(left, top, right, bottom);
boundsChanged = true;
+ this.alignBaselineAtBottom = alignBaselineAtBottom;
}
}
+ public void setExpandedBounds(int left, int top, int right, int bottom) {
+ setExpandedBounds(left, top, right, bottom, /* alignBaselineAtBottom= */ true);
+ }
+
public void setExpandedBounds(@NonNull Rect bounds) {
setExpandedBounds(bounds.left, bounds.top, bounds.right, bounds.bottom);
}
@@ -274,6 +289,17 @@ public void setCollapsedBounds(@NonNull Rect bounds) {
setCollapsedBounds(bounds.left, bounds.top, bounds.right, bounds.bottom);
}
+ public void setCollapsedBoundsForOffsets(int left, int top, int right, int bottom) {
+ if (collapsedBoundsForPlacement == null) {
+ collapsedBoundsForPlacement = new Rect(left, top, right, bottom);
+ boundsChanged = true;
+ }
+ if (!rectEquals(collapsedBoundsForPlacement, left, top, right, bottom)) {
+ collapsedBoundsForPlacement.set(left, top, right, bottom);
+ boundsChanged = true;
+ }
+ }
+
public void getCollapsedTextBottomTextBounds(
@NonNull RectF bounds, int labelWidth, int textGravity) {
isRtl = calculateIsRtl(text);
@@ -365,6 +391,12 @@ public float getCollapsedSingleLineHeight() {
return -tmpPaint.ascent();
}
+ public float getCollapsedFullSingleLineHeight() {
+ getTextPaintCollapsed(tmpPaint);
+ // Return collapsed height measured from the baseline.
+ return -tmpPaint.ascent() + tmpPaint.descent();
+ }
+
public void setCurrentOffsetY(int currentOffsetY) {
this.currentOffsetY = currentOffsetY;
}
@@ -783,33 +815,48 @@ private void calculateBaseOffsets(boolean forceRecalculate) {
collapsedTextGravity,
isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+ Rect collapsedPlacementBounds = collapsedBoundsForPlacement != null
+ ? collapsedBoundsForPlacement : collapsedBounds;
+
switch (collapsedAbsGravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.BOTTOM:
- collapsedDrawY = collapsedBounds.bottom + textPaint.ascent();
+ collapsedDrawY = collapsedPlacementBounds.bottom + textPaint.ascent();
break;
case Gravity.TOP:
- collapsedDrawY = collapsedBounds.top;
+ collapsedDrawY = collapsedPlacementBounds.top;
break;
case Gravity.CENTER_VERTICAL:
default:
float textOffset = (textPaint.descent() - textPaint.ascent()) / 2;
- collapsedDrawY = collapsedBounds.centerY() - textOffset;
+ collapsedDrawY = collapsedPlacementBounds.centerY() - textOffset;
break;
}
switch (collapsedAbsGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
- collapsedDrawX = collapsedBounds.centerX() - (collapsedTextWidth / 2);
+ collapsedDrawX = collapsedPlacementBounds.centerX() - (collapsedTextWidth / 2);
break;
case Gravity.RIGHT:
- collapsedDrawX = collapsedBounds.right - collapsedTextWidth;
+ collapsedDrawX = collapsedPlacementBounds.right - collapsedTextWidth;
break;
case Gravity.LEFT:
default:
- collapsedDrawX = collapsedBounds.left;
+ collapsedDrawX = collapsedPlacementBounds.left;
break;
}
+ // If the collapsed text width and height can fit into the collapsed bounds, try to move it so
+ // it will fit.
+ if (collapsedTextWidth <= collapsedBounds.width()) {
+ collapsedDrawX += max(0, collapsedBounds.left - collapsedDrawX);
+ collapsedDrawX += min(0, collapsedBounds.right - (collapsedDrawX + collapsedTextWidth));
+ }
+ if (getCollapsedFullSingleLineHeight() <= collapsedBounds.height()) {
+ collapsedDrawY += max(0, collapsedBounds.top - collapsedDrawY);
+ collapsedDrawY +=
+ min(0, collapsedBounds.bottom - (collapsedDrawY + getCollapsedTextHeight()));
+ }
+
calculateUsingTextSize(/* fraction= */ 0, forceRecalculate);
float expandedTextHeight = textLayout != null ? textLayout.getHeight() : 0;
float expandedTextWidth = 0;
@@ -826,7 +873,10 @@ private void calculateBaseOffsets(boolean forceRecalculate) {
isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
switch (expandedAbsGravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.BOTTOM:
- expandedDrawY = expandedBounds.bottom - expandedTextHeight + textPaint.descent();
+ expandedDrawY =
+ expandedBounds.bottom
+ - expandedTextHeight
+ + (alignBaselineAtBottom ? textPaint.descent() : 0);
break;
case Gravity.TOP:
expandedDrawY = expandedBounds.top;
@@ -892,7 +942,9 @@ public void draw(@NonNull Canvas canvas) {
float y = currentDrawY;
if (DEBUG_DRAW) {
- // Just a debug tool, which drawn a magenta rect in the text bounds
+ // Just a debug tool, which draws semitransparent magenta rects in the expanded bounds and
+ // text bounds.
+ canvas.drawRect(expandedBounds, DEBUG_DRAW_PAINT);
canvas.drawRect(
x,
y,
@@ -962,9 +1014,9 @@ private void drawMultilineTransition(@NonNull Canvas canvas, float currentExpand
int lineBaseline = textLayout.getLineBaseline(0);
canvas.drawText(
textToDrawCollapsed,
- /* start = */ 0,
+ /* start= */ 0,
textToDrawCollapsed.length(),
- /* x = */ 0,
+ /* x= */ 0,
lineBaseline,
textPaint);
// Reverse workaround for API 31(+). Applying opaque shadow color after the expanded text and
@@ -984,9 +1036,9 @@ private void drawMultilineTransition(@NonNull Canvas canvas, float currentExpand
textPaint.setAlpha(originalAlpha);
canvas.drawText(
tmp,
- /* start = */ 0,
+ /* start= */ 0,
min(textLayout.getLineEnd(0), tmp.length()),
- /* x = */ 0,
+ /* x= */ 0,
lineBaseline,
textPaint);
}
@@ -1157,8 +1209,7 @@ private StaticLayout createStaticLayout(
private Alignment getMultilineTextLayoutAlignment() {
int absoluteGravity =
Gravity.getAbsoluteGravity(
- expandedTextGravity,
- isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+ expandedTextGravity, isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
return ALIGN_CENTER;
diff --git a/lib/java/com/google/android/material/internal/FadeThroughDrawable.java b/lib/java/com/google/android/material/internal/FadeThroughDrawable.java
index a2e38392168..239d4c1ff95 100644
--- a/lib/java/com/google/android/material/internal/FadeThroughDrawable.java
+++ b/lib/java/com/google/android/material/internal/FadeThroughDrawable.java
@@ -23,6 +23,7 @@
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
+import android.util.LayoutDirection;
import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -43,13 +44,25 @@ public class FadeThroughDrawable extends Drawable {
private float progress;
- public FadeThroughDrawable(@NonNull Drawable fadeOutDrawable, @NonNull Drawable fadeInDrawable) {
- this.fadeOutDrawable = fadeOutDrawable.getConstantState().newDrawable().mutate();
- this.fadeInDrawable = fadeInDrawable.getConstantState().newDrawable().mutate();
- DrawableCompat.setLayoutDirection(
- this.fadeOutDrawable, DrawableCompat.getLayoutDirection(fadeOutDrawable));
- DrawableCompat.setLayoutDirection(
- this.fadeInDrawable, DrawableCompat.getLayoutDirection(fadeInDrawable));
+ public FadeThroughDrawable(@Nullable Drawable fadeOutDrawable, @Nullable Drawable fadeInDrawable) {
+ this.fadeOutDrawable =
+ fadeOutDrawable != null
+ ? fadeOutDrawable.getConstantState().newDrawable().mutate()
+ : new EmptyDrawable();
+ this.fadeInDrawable =
+ fadeInDrawable != null
+ ? fadeInDrawable.getConstantState().newDrawable().mutate()
+ : new EmptyDrawable();
+ int outLayoutDir =
+ fadeOutDrawable != null
+ ? DrawableCompat.getLayoutDirection(fadeOutDrawable)
+ : LayoutDirection.LOCALE;
+ int inLayoutDir =
+ fadeInDrawable != null
+ ? DrawableCompat.getLayoutDirection(fadeInDrawable)
+ : LayoutDirection.LOCALE;
+ DrawableCompat.setLayoutDirection(this.fadeOutDrawable, outLayoutDir);
+ DrawableCompat.setLayoutDirection(this.fadeInDrawable, inLayoutDir);
this.fadeInDrawable.setAlpha(0);
this.alphas = new float[2];
}
@@ -135,4 +148,21 @@ public void setProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
invalidateSelf();
}
}
+
+ private static class EmptyDrawable extends Drawable {
+
+ @Override
+ public void draw(@NonNull Canvas canvas) {}
+
+ @Override
+ public void setAlpha(int alpha) {}
+
+ @Override
+ public void setColorFilter(@Nullable ColorFilter colorFilter) {}
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSPARENT;
+ }
+ }
}
diff --git a/lib/java/com/google/android/material/internal/FlowLayout.java b/lib/java/com/google/android/material/internal/FlowLayout.java
index 1ab719d9c10..41f30d71a44 100644
--- a/lib/java/com/google/android/material/internal/FlowLayout.java
+++ b/lib/java/com/google/android/material/internal/FlowLayout.java
@@ -197,8 +197,6 @@ protected void onLayout(boolean sizeChanged, int left, int top, int right, int b
int childBottom = childTop;
int childEnd;
- final int maxChildEnd = right - left - paddingEnd;
-
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
@@ -218,21 +216,28 @@ protected void onLayout(boolean sizeChanged, int left, int top, int right, int b
childEnd = childStart + startMargin + child.getMeasuredWidth();
+ final int maxChildEnd = right - left - paddingEnd;
if (!singleLine && (childEnd > maxChildEnd)) {
childStart = paddingStart;
+ childEnd = childStart + startMargin + child.getMeasuredWidth();
childTop = childBottom + lineSpacing;
rowCount++;
}
child.setTag(R.id.row_index_key, rowCount - 1);
-
- childEnd = childStart + startMargin + child.getMeasuredWidth();
childBottom = childTop + child.getMeasuredHeight();
if (isRtl) {
child.layout(
- maxChildEnd - childEnd, childTop, maxChildEnd - childStart - startMargin, childBottom);
+ /* left= */ right - left - childEnd,
+ /* top= */ childTop,
+ /* right= */ right - left - childStart - startMargin,
+ /* bottom= */ childBottom);
} else {
- child.layout(childStart + startMargin, childTop, childEnd, childBottom);
+ child.layout(
+ /* left= */ childStart + startMargin,
+ /* top= */ childTop,
+ /* right= */ childEnd,
+ /* bottom= */ childBottom);
}
childStart += (startMargin + endMargin + child.getMeasuredWidth()) + itemSpacing;
diff --git a/lib/java/com/google/android/material/internal/NavigationMenuItemView.java b/lib/java/com/google/android/material/internal/NavigationMenuItemView.java
index edb59c12e4e..bd5209e62af 100644
--- a/lib/java/com/google/android/material/internal/NavigationMenuItemView.java
+++ b/lib/java/com/google/android/material/internal/NavigationMenuItemView.java
@@ -234,7 +234,7 @@ public void setIcon(@Nullable Drawable icon) {
if (hasIconTintList) {
Drawable.ConstantState state = icon.getConstantState();
icon = DrawableCompat.wrap(state == null ? icon : state.newDrawable()).mutate();
- DrawableCompat.setTintList(icon, iconTintList);
+ icon.setTintList(iconTintList);
}
icon.setBounds(0, 0, iconSize, iconSize);
} else if (needsEmptyIcon) {
diff --git a/lib/java/com/google/android/material/internal/ThemeEnforcement.java b/lib/java/com/google/android/material/internal/ThemeEnforcement.java
index f0ebe439f1c..246cc462795 100644
--- a/lib/java/com/google/android/material/internal/ThemeEnforcement.java
+++ b/lib/java/com/google/android/material/internal/ThemeEnforcement.java
@@ -41,7 +41,9 @@
@RestrictTo(LIBRARY_GROUP)
public final class ThemeEnforcement {
- private static final int[] APPCOMPAT_CHECK_ATTRS = {R.attr.colorPrimary};
+ private static final int[] APPCOMPAT_CHECK_ATTRS = {
+ androidx.appcompat.R.attr.colorPrimary
+ };
private static final String APPCOMPAT_THEME_NAME = "Theme.AppCompat";
private static final int[] MATERIAL_CHECK_ATTRS = {R.attr.colorPrimaryVariant};
diff --git a/lib/java/com/google/android/material/internal/ViewUtils.java b/lib/java/com/google/android/material/internal/ViewUtils.java
index 81fad714a27..84cf92efe4f 100644
--- a/lib/java/com/google/android/material/internal/ViewUtils.java
+++ b/lib/java/com/google/android/material/internal/ViewUtils.java
@@ -27,8 +27,6 @@
import android.content.res.TypedArray;
import android.graphics.PorterDuff;
import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.ColorStateListDrawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
@@ -37,7 +35,6 @@
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.Dimension;
import androidx.annotation.NonNull;
@@ -332,7 +329,7 @@ public WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat inse
public static void requestApplyInsetsWhenAttached(@NonNull View view) {
if (view.isAttachedToWindow()) {
// We're already attached, just request as normal.
- ViewCompat.requestApplyInsets(view);
+ view.requestApplyInsets();
} else {
// We're not attached to the hierarchy, add a listener to request when we are.
view.addOnAttachStateChangeListener(
@@ -340,7 +337,7 @@ public static void requestApplyInsetsWhenAttached(@NonNull View view) {
@Override
public void onViewAttachedToWindow(@NonNull View v) {
v.removeOnAttachStateChangeListener(this);
- ViewCompat.requestApplyInsets(v);
+ v.requestApplyInsets();
}
@Override
@@ -357,7 +354,7 @@ public static float getParentAbsoluteElevation(@NonNull View view) {
float absoluteElevation = 0;
ViewParent viewParent = view.getParent();
while (viewParent instanceof View) {
- absoluteElevation += ViewCompat.getElevation((View) viewParent);
+ absoluteElevation += ((View) viewParent).getElevation();
viewParent = viewParent.getParent();
}
return absoluteElevation;
diff --git a/lib/java/com/google/android/material/internal/res-public/values/public.xml b/lib/java/com/google/android/material/internal/res-public/values/public.xml
index ae693769e5a..76ac23eb827 100644
--- a/lib/java/com/google/android/material/internal/res-public/values/public.xml
+++ b/lib/java/com/google/android/material/internal/res-public/values/public.xml
@@ -26,5 +26,6 @@
+
diff --git a/lib/java/com/google/android/material/internal/res/values/attrs.xml b/lib/java/com/google/android/material/internal/res/values/attrs.xml
index acab54e6f5e..7cb2a5afe71 100644
--- a/lib/java/com/google/android/material/internal/res/values/attrs.xml
+++ b/lib/java/com/google/android/material/internal/res/values/attrs.xml
@@ -63,6 +63,7 @@
+
diff --git a/lib/java/com/google/android/material/loadingindicator/LoadingIndicator.java b/lib/java/com/google/android/material/loadingindicator/LoadingIndicator.java
index ec6563c905b..5bd237132d9 100644
--- a/lib/java/com/google/android/material/loadingindicator/LoadingIndicator.java
+++ b/lib/java/com/google/android/material/loadingindicator/LoadingIndicator.java
@@ -30,6 +30,7 @@
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ProgressBar;
import androidx.annotation.AttrRes;
@@ -178,10 +179,10 @@ boolean visibleToUser() {
* This is necessary as before API 24, it is not guaranteed that Views will ever be notified
* about their parent changing. Thus, we don't have a proper point to hook in and re-check {@link
* #isShown()} on parent changes that result from {@link
- * android.view.ViewGroup#attachViewToParent(View, int, LayoutParams)}, which *can* change our
- * effective visibility. So this method errs on the side of assuming visibility unless we can
- * conclusively prove otherwise (but may result in some false positives, if this view ends up
- * being attached to a non-visible hierarchy after being detached in a visible state).
+ * android.view.ViewGroup#attachViewToParent(View, int, ViewGroup.LayoutParams)}, which *can*
+ * change our effective visibility. So this method errs on the side of assuming visibility unless
+ * we can conclusively prove otherwise (but may result in some false positives, if this view
+ * ends up being attached to a non-visible hierarchy after being detached in a visible state).
*/
boolean isEffectivelyVisible() {
View current = this;
@@ -273,7 +274,11 @@ public int getContainerHeight() {
public void setIndicatorColor(@ColorInt int... indicatorColors) {
if (indicatorColors.length == 0) {
// Uses theme primary color for indicator by default. Indicator color cannot be empty.
- indicatorColors = new int[] {MaterialColors.getColor(getContext(), R.attr.colorPrimary, -1)};
+ indicatorColors =
+ new int[] {
+ MaterialColors.getColor(
+ getContext(), androidx.appcompat.R.attr.colorPrimary, -1)
+ };
}
if (!Arrays.equals(getIndicatorColor(), indicatorColors)) {
specs.indicatorColors = indicatorColors;
diff --git a/lib/java/com/google/android/material/loadingindicator/LoadingIndicatorDrawable.java b/lib/java/com/google/android/material/loadingindicator/LoadingIndicatorDrawable.java
index 8d5efcc9598..1c5c407d19c 100644
--- a/lib/java/com/google/android/material/loadingindicator/LoadingIndicatorDrawable.java
+++ b/lib/java/com/google/android/material/loadingindicator/LoadingIndicatorDrawable.java
@@ -28,7 +28,6 @@
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.annotation.VisibleForTesting;
-import androidx.core.graphics.drawable.DrawableCompat;
import com.google.android.material.progressindicator.AnimatorDurationScaleProvider;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
@@ -98,7 +97,7 @@ public void draw(@NonNull Canvas canvas) {
if (isSystemAnimatorDisabled() && staticDummyDrawable != null) {
staticDummyDrawable.setBounds(bounds);
- DrawableCompat.setTint(staticDummyDrawable, specs.indicatorColors[0]);
+ staticDummyDrawable.setTint(specs.indicatorColors[0]);
staticDummyDrawable.draw(canvas);
return;
}
diff --git a/lib/java/com/google/android/material/loadingindicator/LoadingIndicatorDrawingDelegate.java b/lib/java/com/google/android/material/loadingindicator/LoadingIndicatorDrawingDelegate.java
index e27e3e61d12..6b6761148ec 100644
--- a/lib/java/com/google/android/material/loadingindicator/LoadingIndicatorDrawingDelegate.java
+++ b/lib/java/com/google/android/material/loadingindicator/LoadingIndicatorDrawingDelegate.java
@@ -32,6 +32,7 @@
import androidx.graphics.shapes.RoundedPolygon;
import androidx.graphics.shapes.Shapes_androidKt;
import com.google.android.material.color.MaterialColors;
+import com.google.android.material.math.MathUtils;
import com.google.android.material.shape.MaterialShapes;
class LoadingIndicatorDrawingDelegate {
@@ -116,9 +117,10 @@ void drawIndicator(
canvas.rotate(indicatorState.rotationDegree);
// Draws the shape morph.
indicatorPath.rewind();
+ int shapeMorphFraction = (int) Math.floor(indicatorState.morphFraction);
int fractionAmongAllShapes =
- (int) (indicatorState.morphFraction % INDETERMINATE_MORPH_SEQUENCE.length);
- float fractionPerShape = indicatorState.morphFraction % 1;
+ MathUtils.floorMod(shapeMorphFraction, INDETERMINATE_MORPH_SEQUENCE.length);
+ float fractionPerShape = indicatorState.morphFraction - shapeMorphFraction;
Shapes_androidKt.toPath(
INDETERMINATE_MORPH_SEQUENCE[fractionAmongAllShapes], fractionPerShape, indicatorPath);
// We need to apply the scaling to the path directly, instead of on the canvas, to avoid the
diff --git a/lib/java/com/google/android/material/loadingindicator/LoadingIndicatorSpec.java b/lib/java/com/google/android/material/loadingindicator/LoadingIndicatorSpec.java
index 85783b58e80..2193a58dc08 100644
--- a/lib/java/com/google/android/material/loadingindicator/LoadingIndicatorSpec.java
+++ b/lib/java/com/google/android/material/loadingindicator/LoadingIndicatorSpec.java
@@ -85,7 +85,10 @@ public LoadingIndicatorSpec(
private void loadIndicatorColors(@NonNull Context context, @NonNull TypedArray typedArray) {
if (!typedArray.hasValue(R.styleable.LoadingIndicator_indicatorColor)) {
// Uses theme primary color for indicator if not provided in the attribute set.
- indicatorColors = new int[] {MaterialColors.getColor(context, R.attr.colorPrimary, -1)};
+ indicatorColors =
+ new int[] {
+ MaterialColors.getColor(context, androidx.appcompat.R.attr.colorPrimary, -1)
+ };
return;
}
diff --git a/lib/java/com/google/android/material/loadingindicator/res/values-af/strings.xml b/lib/java/com/google/android/material/loadingindicator/res/values-af/strings.xml
index 33727cfcc55..b144b9d999b 100644
--- a/lib/java/com/google/android/material/loadingindicator/res/values-af/strings.xml
+++ b/lib/java/com/google/android/material/loadingindicator/res/values-af/strings.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/lib/java/com/google/android/material/materialswitch/MaterialSwitch.java b/lib/java/com/google/android/material/materialswitch/MaterialSwitch.java
index 5b410d3fa90..cda1fd94a53 100644
--- a/lib/java/com/google/android/material/materialswitch/MaterialSwitch.java
+++ b/lib/java/com/google/android/material/materialswitch/MaterialSwitch.java
@@ -35,7 +35,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
-import androidx.core.graphics.drawable.DrawableCompat;
import com.google.android.material.drawable.DrawableUtils;
import com.google.android.material.internal.ThemeEnforcement;
import com.google.android.material.internal.ViewUtils;
@@ -232,7 +231,7 @@ public int getThumbIconSize() {
*
* Subsequent calls to {@link #setThumbIconDrawable(Drawable)} will
* automatically mutate the drawable and apply the specified tint and tint
- * mode using {@link DrawableCompat#setTintList(Drawable, ColorStateList)}.
+ * mode using {@link Drawable#setTintList(ColorStateList)}.
*
* @param tintList the tint to apply, may be {@code null} to clear tint
*
@@ -346,7 +345,7 @@ public Drawable getTrackDecorationDrawable() {
*
*
Subsequent calls to {@link #setTrackDecorationDrawable(Drawable)} will
* automatically mutate the drawable and apply the specified tint and tint
- * mode using {@link DrawableCompat#setTintList(Drawable, ColorStateList)}.
+ * mode using {@link Drawable#setTintList(ColorStateList)}.
*
* @param tintList the tint to apply, may be {@code null} to clear tint
*
@@ -487,11 +486,9 @@ private static void setInterpolatedDrawableTintIfPossible(
return;
}
- DrawableCompat.setTint(
- drawable,
- blendARGB(
- tint.getColorForState(stateUnchecked, 0),
- tint.getColorForState(stateChecked, 0),
- thumbPosition));
+ drawable.setTint(blendARGB(
+ tint.getColorForState(stateUnchecked, 0),
+ tint.getColorForState(stateChecked, 0),
+ thumbPosition));
}
}
diff --git a/lib/java/com/google/android/material/materialswitch/res/values/tokens.xml b/lib/java/com/google/android/material/materialswitch/res/values/tokens.xml
index e43409a4a5d..abfde8a1be3 100644
--- a/lib/java/com/google/android/material/materialswitch/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/materialswitch/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/menu/res/values/styles.xml b/lib/java/com/google/android/material/menu/res/values/styles.xml
index eba586e30a8..0e01a6419d8 100644
--- a/lib/java/com/google/android/material/menu/res/values/styles.xml
+++ b/lib/java/com/google/android/material/menu/res/values/styles.xml
@@ -56,22 +56,22 @@
diff --git a/lib/java/com/google/android/material/menu/res/values/tokens.xml b/lib/java/com/google/android/material/menu/res/values/tokens.xml
index c6e19760da1..0b990c42795 100644
--- a/lib/java/com/google/android/material/menu/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/menu/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/motion/MaterialBackAnimationHelper.java b/lib/java/com/google/android/material/motion/MaterialBackAnimationHelper.java
index 4051843da61..7bb6068853f 100644
--- a/lib/java/com/google/android/material/motion/MaterialBackAnimationHelper.java
+++ b/lib/java/com/google/android/material/motion/MaterialBackAnimationHelper.java
@@ -21,12 +21,12 @@
import android.content.Context;
import android.util.Log;
import android.view.View;
+import android.view.animation.PathInterpolator;
import androidx.activity.BackEventCompat;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
-import androidx.core.view.animation.PathInterpolatorCompat;
/**
* Base helper class for views that support back handling, which assists with common animation
@@ -43,8 +43,7 @@ public abstract class MaterialBackAnimationHelper {
private static final int CANCEL_DURATION_DEFAULT = 100;
@NonNull
- private final TimeInterpolator progressInterpolator =
- PathInterpolatorCompat.create(0.1f, 0.1f, 0, 1);
+ private final TimeInterpolator progressInterpolator = new PathInterpolator(0.1f, 0.1f, 0, 1);
@NonNull protected final V view;
protected final int hideDurationMax;
diff --git a/lib/java/com/google/android/material/motion/MotionUtils.java b/lib/java/com/google/android/material/motion/MotionUtils.java
index e6b2aca6b9a..1dfbeb555a4 100644
--- a/lib/java/com/google/android/material/motion/MotionUtils.java
+++ b/lib/java/com/google/android/material/motion/MotionUtils.java
@@ -15,14 +15,19 @@
*/
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.core.view.animation.PathInterpolatorCompat;
+import androidx.dynamicanimation.animation.SpringForce;
import com.google.android.material.resources.MaterialAttributes;
/** A utility class for motion system functions. */
@@ -36,6 +41,47 @@ public class MotionUtils {
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.
*
@@ -100,10 +146,10 @@ private static TimeInterpolator getLegacyThemeInterpolator(String easingString)
float controlY1 = getLegacyControlPoint(controlPoints, 1);
float controlX2 = getLegacyControlPoint(controlPoints, 2);
float controlY2 = getLegacyControlPoint(controlPoints, 3);
- return PathInterpolatorCompat.create(controlX1, controlY1, controlX2, controlY2);
+ return new PathInterpolator(controlX1, controlY1, controlX2, controlY2);
} else if (isLegacyEasingType(easingString, EASING_TYPE_PATH)) {
String path = getLegacyEasingContent(easingString, EASING_TYPE_PATH);
- return PathInterpolatorCompat.create(PathParser.createPathFromPathData(path));
+ return new PathInterpolator(PathParser.createPathFromPathData(path));
} else {
throw new IllegalArgumentException("Invalid motion easing type: " + easingString);
}
diff --git a/lib/java/com/google/android/material/motion/res-public/values/public.xml b/lib/java/com/google/android/material/motion/res-public/values/public.xml
index 5f57d2b4869..ea6d97e4421 100644
--- a/lib/java/com/google/android/material/motion/res-public/values/public.xml
+++ b/lib/java/com/google/android/material/motion/res-public/values/public.xml
@@ -15,7 +15,15 @@
~ limitations under the License.
-->
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/motion/res/values/attrs.xml b/lib/java/com/google/android/material/motion/res/values/attrs.xml
index f39991cc459..2293849d1b8 100644
--- a/lib/java/com/google/android/material/motion/res/values/attrs.xml
+++ b/lib/java/com/google/android/material/motion/res/values/attrs.xml
@@ -1,4 +1,5 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/motion/res/values/styles.xml b/lib/java/com/google/android/material/motion/res/values/styles.xml
new file mode 100644
index 00000000000..8bc8802e707
--- /dev/null
+++ b/lib/java/com/google/android/material/motion/res/values/styles.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/motion/res/values/tokens.xml b/lib/java/com/google/android/material/motion/res/values/tokens.xml
index f0739d6d6c2..c93fc413586 100644
--- a/lib/java/com/google/android/material/motion/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/motion/res/values/tokens.xml
@@ -15,11 +15,25 @@
~ limitations under the License.
-->
-
+
-
+
+
+ - 0.9
+ - 1400
+ - 1
+ - 3800
+ - 0.9
+ - 700
+ - 1
+ - 1600
+ - 0.9
+ - 300
+ - 1
+ - 800
path(M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1)
diff --git a/lib/java/com/google/android/material/navigation/DividerMenuItem.java b/lib/java/com/google/android/material/navigation/DividerMenuItem.java
new file mode 100644
index 00000000000..8105166a3db
--- /dev/null
+++ b/lib/java/com/google/android/material/navigation/DividerMenuItem.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2024 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.navigation;
+
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.view.ActionProvider;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * An empty MenuItem that is used to represent a divider in the menu.
+ */
+class DividerMenuItem implements MenuItem {
+
+ @Override
+ public boolean collapseActionView() {
+ return false;
+ }
+
+ @Override
+ public boolean expandActionView() {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public ActionProvider getActionProvider() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public View getActionView() {
+ return null;
+ }
+
+ @Override
+ public char getAlphabeticShortcut() {
+ return 0;
+ }
+
+ @Override
+ public int getGroupId() {
+ return 0;
+ }
+
+ @Nullable
+ @Override
+ public Drawable getIcon() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Intent getIntent() {
+ return null;
+ }
+
+ @Override
+ public int getItemId() {
+ return 0;
+ }
+
+ @Nullable
+ @Override
+ public ContextMenuInfo getMenuInfo() {
+ return null;
+ }
+
+ @Override
+ public char getNumericShortcut() {
+ return 0;
+ }
+
+ @Override
+ public int getOrder() {
+ return 0;
+ }
+
+ @Nullable
+ @Override
+ public SubMenu getSubMenu() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public CharSequence getTitle() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public CharSequence getTitleCondensed() {
+ return null;
+ }
+
+ @Override
+ public boolean hasSubMenu() {
+ return false;
+ }
+
+ @Override
+ public boolean isActionViewExpanded() {
+ return false;
+ }
+
+ @Override
+ public boolean isCheckable() {
+ return false;
+ }
+
+ @Override
+ public boolean isChecked() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isVisible() {
+ return false;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setActionProvider(@Nullable ActionProvider actionProvider) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setActionView(@Nullable View view) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setActionView(int resId) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setAlphabeticShortcut(char alphaChar) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setCheckable(boolean checkable) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setChecked(boolean checked) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setEnabled(boolean enabled) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setIcon(@Nullable Drawable icon) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setIcon(int iconRes) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setIntent(@Nullable Intent intent) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setNumericShortcut(char numericChar) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setOnActionExpandListener(@Nullable OnActionExpandListener listener) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setOnMenuItemClickListener(
+ @Nullable OnMenuItemClickListener menuItemClickListener) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setShortcut(char numericChar, char alphaChar) {
+ return null;
+ }
+
+ @Override
+ public void setShowAsAction(int actionEnum) {
+
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setShowAsActionFlags(int actionEnum) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setTitle(int title) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setTitle(@Nullable CharSequence title) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setTitleCondensed(@Nullable CharSequence title) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MenuItem setVisible(boolean visible) {
+ return null;
+ }
+}
diff --git a/lib/java/com/google/android/material/navigation/NavigationBarDividerView.java b/lib/java/com/google/android/material/navigation/NavigationBarDividerView.java
new file mode 100644
index 00000000000..b47a911cd48
--- /dev/null
+++ b/lib/java/com/google/android/material/navigation/NavigationBarDividerView.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 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.navigation;
+
+import com.google.android.material.R;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import androidx.appcompat.view.menu.MenuItemImpl;
+import android.view.LayoutInflater;
+import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+/**
+ * Provides a view that will be used to render subheader items inside a {@link
+ * NavigationBarMenuView}.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class NavigationBarDividerView extends FrameLayout implements NavigationBarMenuItemView {
+
+ private boolean expanded;
+ boolean onlyShowWhenExpanded;
+ private boolean dividersEnabled;
+
+ NavigationBarDividerView(@NonNull Context context) {
+ super(context);
+ LayoutInflater.from(context).inflate(R.layout.m3_navigation_menu_divider, this, true);
+ updateVisibility();
+ }
+
+ @Override
+ public void initialize(@NonNull MenuItemImpl menuItem, int i) {
+ updateVisibility();
+ }
+
+ @Override
+ @Nullable
+ public MenuItemImpl getItemData() {
+ return null;
+ }
+
+ @Override
+ public void setTitle(@Nullable CharSequence charSequence) {}
+
+ @Override
+ public void setEnabled(boolean enabled) {}
+
+ @Override
+ public void setCheckable(boolean checkable) {}
+
+ @Override
+ public void setChecked(boolean checked) {}
+
+ @Override
+ public void setShortcut(boolean showShortcut, char shortcutKey) {}
+
+ @Override
+ public void setIcon(@Nullable Drawable drawable) {}
+
+ @Override
+ public boolean prefersCondensedTitle() {
+ return false;
+ }
+
+ @Override
+ public boolean showsIcon() {
+ return false;
+ }
+
+ @Override
+ public void setExpanded(boolean expanded) {
+ this.expanded = expanded;
+ updateVisibility();
+ }
+
+ @Override
+ public boolean isExpanded() {
+ return this.expanded;
+ }
+
+ @Override
+ public void setOnlyShowWhenExpanded(boolean onlyShowWhenExpanded) {
+ this.onlyShowWhenExpanded = onlyShowWhenExpanded;
+ updateVisibility();
+ }
+
+ @Override
+ public boolean isOnlyVisibleWhenExpanded() {
+ return this.onlyShowWhenExpanded;
+ }
+
+ public void updateVisibility() {
+ setVisibility(dividersEnabled && (expanded || !onlyShowWhenExpanded) ? VISIBLE : GONE);
+ }
+
+ public void setDividersEnabled(boolean dividersEnabled) {
+ this.dividersEnabled = dividersEnabled;
+ updateVisibility();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+}
diff --git a/lib/java/com/google/android/material/navigation/NavigationBarItemView.java b/lib/java/com/google/android/material/navigation/NavigationBarItemView.java
index b76a8e9c5e2..6c27b45292b 100644
--- a/lib/java/com/google/android/material/navigation/NavigationBarItemView.java
+++ b/lib/java/com/google/android/material/navigation/NavigationBarItemView.java
@@ -30,6 +30,7 @@
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
@@ -60,7 +61,6 @@
import androidx.annotation.Px;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
-import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
@@ -166,6 +166,8 @@ public abstract class NavigationBarItemView extends FrameLayout
private boolean expanded = false;
private boolean onlyShowWhenExpanded = false;
private boolean measurePaddingFromBaseline = false;
+ private boolean scaleLabelSizeWithFont = false;
+ private Rect itemActiveIndicatorExpandedPadding = new Rect();
public NavigationBarItemView(@NonNull Context context) {
super(context);
@@ -208,20 +210,37 @@ public NavigationBarItemView(@NonNull Context context) {
if (icon.getVisibility() == VISIBLE) {
tryUpdateBadgeBounds(icon);
}
- // If item icon gravity is start, we want to update the active indicator width in a layout
- // change listener to keep the active indicator size up to date with the content width.
LayoutParams lp = (LayoutParams) innerContentContainer.getLayoutParams();
int newWidth = right - left + lp.rightMargin + lp.leftMargin;
+ int newHeight = bottom - top + lp.topMargin + lp.bottomMargin;
+ // If item icon gravity is start, we want to update the active indicator width in a layout
+ // change listener to keep the active indicator size up to date with the content width.
if (itemIconGravity == ITEM_ICON_GRAVITY_START
- && activeIndicatorExpandedDesiredWidth == ACTIVE_INDICATOR_WIDTH_WRAP_CONTENT
- && newWidth != activeIndicatorView.getMeasuredWidth()) {
+ && activeIndicatorExpandedDesiredWidth == ACTIVE_INDICATOR_WIDTH_WRAP_CONTENT) {
+
LayoutParams indicatorParams = (LayoutParams) activeIndicatorView.getLayoutParams();
- int minWidth =
- min(
- activeIndicatorDesiredWidth,
- getMeasuredWidth() - (activeIndicatorMarginHorizontal * 2));
- indicatorParams.width = max(newWidth, minWidth);
- activeIndicatorView.setLayoutParams(indicatorParams);
+ boolean layoutParamsChanged = false;
+ if (activeIndicatorExpandedDesiredWidth == ACTIVE_INDICATOR_WIDTH_WRAP_CONTENT
+ && activeIndicatorView.getMeasuredWidth() != newWidth) {
+ int minWidth =
+ min(
+ activeIndicatorDesiredWidth,
+ getMeasuredWidth() - (activeIndicatorMarginHorizontal * 2));
+ indicatorParams.width = max(newWidth, minWidth);
+ layoutParamsChanged = true;
+ }
+
+ // We expect the active indicator height to be larger than the height of the
+ // inner content due to having a min height, but if it is smaller (for example due to
+ // the text content changing to be multi-line) it should encompass that
+ if (activeIndicatorView.getMeasuredHeight() < newHeight) {
+ indicatorParams.height = newHeight;
+ layoutParamsChanged = true;
+ }
+
+ if (layoutParamsChanged) {
+ activeIndicatorView.setLayoutParams(indicatorParams);
+ }
}
});
}
@@ -373,7 +392,10 @@ private void addDefaultExpandedLabelGroupViews() {
}
private void updateItemIconGravity() {
- int sideMargin = 0;
+ int leftMargin = 0;
+ int rightMargin = 0;
+ int topMargin = 0;
+ int bottomMargin = 0;
int sidePadding = 0;
badgeFixedEdge = BadgeDrawable.BADGE_FIXED_EDGE_START;
int verticalLabelGroupVisibility = VISIBLE;
@@ -383,9 +405,10 @@ private void updateItemIconGravity() {
if (expandedLabelGroup.getParent() == null) {
addDefaultExpandedLabelGroupViews();
}
- sideMargin =
- getResources()
- .getDimensionPixelSize(R.dimen.m3_navigation_item_leading_trailing_space);
+ leftMargin = itemActiveIndicatorExpandedPadding.left;
+ rightMargin = itemActiveIndicatorExpandedPadding.right;
+ topMargin = itemActiveIndicatorExpandedPadding.top;
+ bottomMargin = itemActiveIndicatorExpandedPadding.bottom;
badgeFixedEdge = BadgeDrawable.BADGE_FIXED_EDGE_END;
sidePadding = activeIndicatorExpandedMarginHorizontal;
verticalLabelGroupVisibility = GONE;
@@ -398,8 +421,10 @@ private void updateItemIconGravity() {
contentContainerLp.gravity = itemGravity;
FrameLayout.LayoutParams innerContentLp =
(LayoutParams) innerContentContainer.getLayoutParams();
- innerContentLp.leftMargin = sideMargin;
- innerContentLp.rightMargin = sideMargin;
+ innerContentLp.leftMargin = leftMargin;
+ innerContentLp.rightMargin = rightMargin;
+ innerContentLp.topMargin = topMargin;
+ innerContentLp.bottomMargin = bottomMargin;
setPadding(sidePadding, 0, sidePadding, 0);
updateActiveIndicatorLayoutParams(getWidth());
@@ -563,8 +588,8 @@ private void setLayoutConfigurationIconAndLabel(
itemGravity);
setViewMarginAndGravity(
innerContentContainer,
- 0,
- 0,
+ itemIconGravity == ITEM_ICON_GRAVITY_TOP ? 0 : itemActiveIndicatorExpandedPadding.top,
+ itemIconGravity == ITEM_ICON_GRAVITY_TOP ? 0 : itemActiveIndicatorExpandedPadding.bottom,
itemIconGravity == ITEM_ICON_GRAVITY_TOP
? Gravity.CENTER
: Gravity.START | Gravity.CENTER_VERTICAL);
@@ -765,7 +790,7 @@ public void setIcon(@Nullable Drawable iconDrawable) {
DrawableCompat.wrap(state == null ? iconDrawable : state.newDrawable()).mutate();
wrappedIconDrawable = iconDrawable;
if (iconTint != null) {
- DrawableCompat.setTintList(wrappedIconDrawable, iconTint);
+ wrappedIconDrawable.setTintList(iconTint);
}
}
this.icon.setImageDrawable(iconDrawable);
@@ -784,7 +809,7 @@ public boolean showsIcon() {
public void setIconTintList(@Nullable ColorStateList tint) {
iconTint = tint;
if (itemData != null && wrappedIconDrawable != null) {
- DrawableCompat.setTintList(wrappedIconDrawable, iconTint);
+ wrappedIconDrawable.setTintList(iconTint);
wrappedIconDrawable.invalidateSelf();
}
}
@@ -819,12 +844,28 @@ public void setMeasureBottomPaddingFromLabelBaseline(boolean measurePaddingFromB
requestLayout();
}
+ public void setLabelFontScalingEnabled(boolean scaleLabelSizeWithFont) {
+ this.scaleLabelSizeWithFont = scaleLabelSizeWithFont;
+ setTextAppearanceActive(textAppearanceActive);
+ setTextAppearanceInactive(textAppearanceInactive);
+ setHorizontalTextAppearanceActive(horizontalTextAppearanceActive);
+ setHorizontalTextAppearanceInactive(horizontalTextAppearanceInactive);
+ }
+
+ private void setTextAppearanceForLabel(TextView label, int textAppearance) {
+ if (scaleLabelSizeWithFont) {
+ TextViewCompat.setTextAppearance(label, textAppearance);
+ } else {
+ setTextAppearanceWithoutFontScaling(label, textAppearance);
+ }
+ }
+
private void updateInactiveLabelTextAppearance(
@Nullable TextView smallLabel, @StyleRes int textAppearanceInactive) {
if (smallLabel == null) {
return;
}
- setTextAppearanceWithoutFontScaling(smallLabel, textAppearanceInactive);
+ setTextAppearanceForLabel(smallLabel, textAppearanceInactive);
calculateTextScaleFactors();
smallLabel.setMinimumHeight(
MaterialResources.getUnscaledLineHeight(
@@ -841,7 +882,7 @@ private void updateActiveLabelTextAppearance(
if (largeLabel == null) {
return;
}
- setTextAppearanceWithoutFontScaling(largeLabel, textAppearanceActive);
+ setTextAppearanceForLabel(largeLabel, textAppearanceActive);
calculateTextScaleFactors();
largeLabel.setMinimumHeight(
MaterialResources.getUnscaledLineHeight(
@@ -911,6 +952,37 @@ private static void setTextAppearanceWithoutFontScaling(
}
}
+ public void setLabelMaxLines(int labelMaxLines) {
+ smallLabel.setMaxLines(labelMaxLines);
+ largeLabel.setMaxLines(labelMaxLines);
+ expandedSmallLabel.setMaxLines(labelMaxLines);
+ expandedLargeLabel.setMaxLines(labelMaxLines);
+
+ // Due to b/316260445 that was fixed in V+, text with ellipses may be cut off when centered
+ // due to letter spacing being miscalculated for the ellipses character. We only center the text
+ // in the following scenarios:
+ // 1. API level is greater than 34, OR
+ // 2. The text is not cut off by an ellipses
+ if (VERSION.SDK_INT > VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ smallLabel.setGravity(Gravity.CENTER);
+ largeLabel.setGravity(Gravity.CENTER);
+ } else if (labelMaxLines > 1) {
+ // If not single-line, remove the ellipses and center. Removing the ellipses is an unfortunate
+ // tradeoff due to this bug. We do not want to remove the ellipses for single-line text
+ // because centering text is not useful (since the textview is centered already) and we
+ // would rather keep the ellipses.
+ smallLabel.setEllipsize(null);
+ largeLabel.setEllipsize(null);
+ smallLabel.setGravity(Gravity.CENTER);
+ largeLabel.setGravity(Gravity.CENTER);
+ } else {
+ smallLabel.setGravity(Gravity.CENTER_VERTICAL);
+ largeLabel.setGravity(Gravity.CENTER_VERTICAL);
+ }
+
+ requestLayout();
+ }
+
public void setTextColor(@Nullable ColorStateList color) {
textColor = color;
if (color != null) {
@@ -936,8 +1008,7 @@ private void calculateTextScaleFactors() {
}
public void setItemBackground(int background) {
- Drawable backgroundDrawable =
- background == 0 ? null : ContextCompat.getDrawable(getContext(), background);
+ Drawable backgroundDrawable = background == 0 ? null : getContext().getDrawable(background);
setItemBackground(backgroundDrawable);
}
@@ -1104,13 +1175,22 @@ public void setActiveIndicatorExpandedHeight(int height) {
updateActiveIndicatorLayoutParams(getWidth());
}
+ /**
+ * Set the padding of the active indicator when it is expanded to wrap its content.
+ *
+ * @param itemActiveIndicatorExpandedPadding the Rect containing the padding information
+ */
+ public void setActiveIndicatorExpandedPadding(@NonNull Rect itemActiveIndicatorExpandedPadding) {
+ this.itemActiveIndicatorExpandedPadding = itemActiveIndicatorExpandedPadding;
+ }
+
/**
* Update the active indicators width and height for the available width and label visibility
* mode.
*
* @param availableWidth The total width of this item layout.
*/
- private void updateActiveIndicatorLayoutParams(int availableWidth) {
+ public void updateActiveIndicatorLayoutParams(int availableWidth) {
// Set width to the min of either the desired indicator width or the available width minus
// a horizontal margin.
if (availableWidth <= 0 && getVisibility() == VISIBLE) {
@@ -1133,7 +1213,8 @@ private void updateActiveIndicatorLayoutParams(int availableWidth) {
} else {
newWidth = min(activeIndicatorExpandedDesiredWidth, adjustedAvailableWidth);
}
- newHeight = activeIndicatorExpandedDesiredHeight;
+ newHeight =
+ max(activeIndicatorExpandedDesiredHeight, innerContentContainer.getMeasuredHeight());
}
LayoutParams indicatorParams = (LayoutParams) activeIndicatorView.getLayoutParams();
// If the label visibility is unlabeled, make the active indicator's height equal to its
diff --git a/lib/java/com/google/android/material/navigation/NavigationBarMenuBuilder.java b/lib/java/com/google/android/material/navigation/NavigationBarMenuBuilder.java
index 0d04cd6644e..92339d6851f 100644
--- a/lib/java/com/google/android/material/navigation/NavigationBarMenuBuilder.java
+++ b/lib/java/com/google/android/material/navigation/NavigationBarMenuBuilder.java
@@ -48,8 +48,9 @@ public class NavigationBarMenuBuilder {
}
/**
- * Returns total number of items in the menu, including submenus and submenu items. For example,
- * a Menu with items {Item, SubMenu, SubMenuItem} would have a size of 3.
+ * Returns total number of items in the menu, including submenus, submenu items, and dividers. For
+ * example, a Menu with items {Item, Divider, SubMenu, SubMenuItem, Divider} would have a size of
+ * 5.
*/
public int size() {
return items.size();
@@ -103,8 +104,13 @@ public void refreshItems() {
visibleMainItemCount = 0;
for (int i = 0; i < menuBuilder.size(); i++) {
MenuItem item = menuBuilder.getItem(i);
- items.add(item);
if (item.hasSubMenu()) {
+ if (!items.isEmpty()
+ && !(items.get(items.size() - 1) instanceof DividerMenuItem)
+ && item.isVisible()) {
+ items.add(new DividerMenuItem());
+ }
+ items.add(item);
SubMenu subMenu = item.getSubMenu();
for (int j = 0; j < subMenu.size(); j++) {
MenuItem submenuItem = subMenu.getItem(j);
@@ -117,7 +123,9 @@ public void refreshItems() {
visibleContentItemCount++;
}
}
+ items.add(new DividerMenuItem());
} else {
+ items.add(item);
contentItemCount++;
if (item.isVisible()) {
visibleContentItemCount++;
@@ -125,5 +133,9 @@ public void refreshItems() {
}
}
}
+
+ if (!items.isEmpty() && items.get(items.size()-1) instanceof DividerMenuItem) {
+ items.remove(items.size()-1);
+ }
}
}
diff --git a/lib/java/com/google/android/material/navigation/NavigationBarMenuView.java b/lib/java/com/google/android/material/navigation/NavigationBarMenuView.java
index 6c7d1b2b073..a1ad151ed92 100644
--- a/lib/java/com/google/android/material/navigation/NavigationBarMenuView.java
+++ b/lib/java/com/google/android/material/navigation/NavigationBarMenuView.java
@@ -24,6 +24,7 @@
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.view.menu.MenuBuilder;
@@ -122,6 +123,8 @@ public abstract class NavigationBarMenuView extends ViewGroup implements MenuVie
private NavigationBarPresenter presenter;
private NavigationBarMenuBuilder menu;
private boolean measurePaddingFromLabelBaseline;
+ private boolean scaleLabelWithFont;
+ private int labelMaxLines = 1;
private int itemPoolSize = 0;
private boolean expanded;
@@ -129,6 +132,8 @@ public abstract class NavigationBarMenuView extends ViewGroup implements MenuVie
private static final int DEFAULT_COLLAPSED_MAX_COUNT = 7;
private int collapsedMaxItemCount = DEFAULT_COLLAPSED_MAX_COUNT;
+ private boolean dividersEnabled = false;
+ private final Rect itemActiveIndicatorExpandedPadding = new Rect();
public NavigationBarMenuView(@NonNull Context context) {
super(context);
@@ -182,7 +187,7 @@ public void setCheckedItem(@NonNull MenuItem checkedItem) {
return;
}
// Unset the previous checked item
- if (this.checkedItem != null) {
+ if (this.checkedItem != null && this.checkedItem.isChecked()) {
this.checkedItem.setChecked(false);
}
checkedItem.setChecked(true);
@@ -502,6 +507,38 @@ public void setMeasurePaddingFromLabelBaseline(boolean measurePaddingFromLabelBa
}
}
+ public void setLabelFontScalingEnabled(boolean scaleLabelWithFont) {
+ this.scaleLabelWithFont = scaleLabelWithFont;
+ if (buttons != null) {
+ for (NavigationBarMenuItemView item : buttons) {
+ if (item instanceof NavigationBarItemView) {
+ ((NavigationBarItemView) item)
+ .setLabelFontScalingEnabled(scaleLabelWithFont);
+ }
+ }
+ }
+ }
+
+ public boolean getScaleLabelTextWithFont() {
+ return scaleLabelWithFont;
+ }
+
+ public void setLabelMaxLines(int labelMaxLines) {
+ this.labelMaxLines = labelMaxLines;
+ if (buttons != null) {
+ for (NavigationBarMenuItemView item : buttons) {
+ if (item instanceof NavigationBarItemView) {
+ ((NavigationBarItemView) item)
+ .setLabelMaxLines(labelMaxLines);
+ }
+ }
+ }
+ }
+
+ public int getLabelMaxLines() {
+ return labelMaxLines;
+ }
+
/**
* Get the distance between the item's active indicator container and the label.
*/
@@ -769,6 +806,30 @@ public void setItemActiveIndicatorExpandedMarginHorizontal(@Px int marginHorizon
}
}
+ /**
+ * Set the padding of the expanded active indicator wrapping the content.
+ *
+ * @param paddingLeft The left padding, in pixels.
+ * @param paddingTop The top padding, in pixels.
+ * @param paddingRight The right padding, in pixels.
+ * @param paddingBottom The bottom padding, in pixels.
+ */
+ public void setItemActiveIndicatorExpandedPadding(int paddingLeft, int paddingTop,
+ int paddingRight, int paddingBottom) {
+ itemActiveIndicatorExpandedPadding.left = paddingLeft;
+ itemActiveIndicatorExpandedPadding.top = paddingTop;
+ itemActiveIndicatorExpandedPadding.right = paddingRight;
+ itemActiveIndicatorExpandedPadding.bottom = paddingBottom;
+ if (buttons != null) {
+ for (NavigationBarMenuItemView item : buttons) {
+ if (item instanceof NavigationBarItemView) {
+ ((NavigationBarItemView) item)
+ .setActiveIndicatorExpandedPadding(itemActiveIndicatorExpandedPadding);
+ }
+ }
+ }
+ }
+
/**
* Get the {@link ShapeAppearanceModel} of the active indicator drawable.
*
@@ -1062,6 +1123,7 @@ private NavigationBarItemView createMenuItem(
presenter.setUpdateSuspended(false);
NavigationBarItemView child = getNewItem();
child.setShifting(shifting);
+ child.setLabelMaxLines(labelMaxLines);
child.setIconTintList(itemIconTint);
child.setIconSize(itemIconSize);
// Set the text color the default, then look for another text color in order of precedence.
@@ -1079,6 +1141,7 @@ private NavigationBarItemView createMenuItem(
child.setItemPaddingBottom(itemPaddingBottom);
}
child.setMeasureBottomPaddingFromLabelBaseline(measurePaddingFromLabelBaseline);
+ child.setLabelFontScalingEnabled(scaleLabelWithFont);
if (itemActiveIndicatorLabelPadding != NO_PADDING) {
child.setActiveIndicatorLabelPadding(itemActiveIndicatorLabelPadding);
}
@@ -1091,6 +1154,7 @@ private NavigationBarItemView createMenuItem(
child.setActiveIndicatorExpandedHeight(itemActiveIndicatorExpandedHeight);
child.setActiveIndicatorMarginHorizontal(itemActiveIndicatorMarginHorizontal);
child.setItemGravity(itemGravity);
+ child.setActiveIndicatorExpandedPadding(itemActiveIndicatorExpandedPadding);
child.setActiveIndicatorExpandedMarginHorizontal(itemActiveIndicatorExpandedMarginHorizontal);
child.setActiveIndicatorDrawable(createItemActiveIndicatorDrawable());
child.setActiveIndicatorResizeable(itemActiveIndicatorResizeable);
@@ -1150,7 +1214,12 @@ public void buildMenuView() {
for (int i = 0; i < menuSize; i++) {
MenuItem menuItem = menu.getItemAt(i);
NavigationBarMenuItemView child;
- if (menuItem.hasSubMenu()) {
+ if (menuItem instanceof DividerMenuItem) {
+ // Add a divider
+ child = new NavigationBarDividerView(getContext());
+ child.setOnlyShowWhenExpanded(true);
+ ((NavigationBarDividerView) child).setDividersEnabled(dividersEnabled);
+ } else if (menuItem.hasSubMenu()) {
if (nextSubheaderItemCount > 0) {
// We do not support submenus inside submenus. If there is still subheader items to be
// instantiated, we should not have another submenu.
@@ -1177,7 +1246,9 @@ public void buildMenuView() {
i, (MenuItemImpl) menuItem, shifting, collapsedItemsSoFar >= collapsedMaxItemCount);
collapsedItemsSoFar++;
}
- if (menuItem.isCheckable() && selectedItemPosition == NO_SELECTED_ITEM) {
+ if (!(menuItem instanceof DividerMenuItem)
+ && menuItem.isCheckable()
+ && selectedItemPosition == NO_SELECTED_ITEM) {
selectedItemPosition = i;
}
buttons[i] = child;
@@ -1192,11 +1263,19 @@ private boolean isMenuStructureSame() {
return false;
}
for (int i = 0; i < buttons.length; i++) {
- if (menu.getItemAt(i).hasSubMenu()
- ? buttons[i] instanceof NavigationBarItemView
- : buttons[i] instanceof NavigationBarSubheaderView) {
+ // If the menu item is a divider but the existing item is not a divider, return false
+ if (menu.getItemAt(i) instanceof DividerMenuItem
+ && !(buttons[i] instanceof NavigationBarDividerView)) {
return false;
}
+ boolean incorrectSubheaderType =
+ menu.getItemAt(i).hasSubMenu() && !(buttons[i] instanceof NavigationBarSubheaderView);
+ boolean incorrectItemType =
+ !menu.getItemAt(i).hasSubMenu() && !(buttons[i] instanceof NavigationBarItemView);
+ if (!(menu.getItemAt(i) instanceof DividerMenuItem)
+ && (incorrectSubheaderType || incorrectItemType)) {
+ return false;
+ }
}
return true;
}
@@ -1242,7 +1321,9 @@ public void updateMenuView() {
itemView.setItemGravity(itemGravity);
itemView.setShifting(shifting);
}
- buttons[i].initialize((MenuItemImpl) menu.getItemAt(i), 0);
+ if (menu.getItemAt(i) instanceof MenuItemImpl) {
+ buttons[i].initialize((MenuItemImpl) menu.getItemAt(i), 0);
+ }
presenter.setUpdateSuspended(false);
}
}
@@ -1255,6 +1336,20 @@ private NavigationBarItemView getNewItem() {
return item;
}
+ public void setSubmenuDividersEnabled(boolean dividersEnabled) {
+ if (this.dividersEnabled == dividersEnabled) {
+ return;
+ }
+ this.dividersEnabled = dividersEnabled;
+ if (buttons != null) {
+ for (NavigationBarMenuItemView itemView : buttons) {
+ if (itemView instanceof NavigationBarDividerView) {
+ ((NavigationBarDividerView) itemView).setDividersEnabled(dividersEnabled);
+ }
+ }
+ }
+ }
+
public void setCollapsedMaxItemCount(int collapsedMaxCount) {
this.collapsedMaxItemCount = collapsedMaxCount;
}
@@ -1416,4 +1511,14 @@ private void validateMenuItemId(int viewId) {
throw new IllegalArgumentException(viewId + " is not a valid view id");
}
}
+
+ public void updateActiveIndicator(int availableWidth) {
+ if (buttons != null) {
+ for (NavigationBarMenuItemView item : buttons) {
+ if (item instanceof NavigationBarItemView) {
+ ((NavigationBarItemView) item).updateActiveIndicatorLayoutParams(availableWidth);
+ }
+ }
+ }
+ }
}
diff --git a/lib/java/com/google/android/material/navigation/NavigationBarView.java b/lib/java/com/google/android/material/navigation/NavigationBarView.java
index d63ad267547..839478bd9d5 100644
--- a/lib/java/com/google/android/material/navigation/NavigationBarView.java
+++ b/lib/java/com/google/android/material/navigation/NavigationBarView.java
@@ -51,7 +51,6 @@
import androidx.annotation.Px;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
-import androidx.core.graphics.drawable.DrawableCompat;
import androidx.customview.view.AbsSavedState;
import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.drawable.DrawableUtils;
@@ -301,7 +300,7 @@ public NavigationBarView(
ColorStateList backgroundTint =
MaterialResources.getColorStateList(
context, attributes, R.styleable.NavigationBarView_backgroundTint);
- DrawableCompat.setTintList(getBackground().mutate(), backgroundTint);
+ getBackground().mutate().setTintList(backgroundTint);
setLabelVisibilityMode(
attributes.getInteger(
@@ -327,6 +326,12 @@ public NavigationBarView(
setMeasureBottomPaddingFromLabelBaseline(attributes.getBoolean(
R.styleable.NavigationBarView_measureBottomPaddingFromLabelBaseline, true));
+ setLabelFontScalingEnabled(
+ attributes.getBoolean(R.styleable.NavigationBarView_labelFontScalingEnabled, false));
+
+ setLabelMaxLines(
+ attributes.getInteger(R.styleable.NavigationBarView_labelMaxLines, 1));
+
int activeIndicatorStyleResId =
attributes.getResourceId(R.styleable.NavigationBarView_itemActiveIndicatorStyle, 0);
@@ -383,6 +388,29 @@ public NavigationBarView(
itemActiveIndicatorMarginHorizontal);
setItemActiveIndicatorExpandedMarginHorizontal(itemActiveIndicatorExpandedMarginHorizontal);
+ int activeIndicatorExpandedDefaultStartEndPadding = getResources()
+ .getDimensionPixelSize(R.dimen.m3_navigation_item_leading_trailing_space);
+ int activeIndicatorExpandedStartPadding =
+ activeIndicatorAttributes.getDimensionPixelOffset(
+ R.styleable.NavigationBarActiveIndicator_expandedActiveIndicatorPaddingStart,
+ activeIndicatorExpandedDefaultStartEndPadding);
+ int activeIndicatorExpandedEndPadding =
+ activeIndicatorAttributes.getDimensionPixelOffset(
+ R.styleable.NavigationBarActiveIndicator_expandedActiveIndicatorPaddingEnd,
+ activeIndicatorExpandedDefaultStartEndPadding);
+
+ setItemActiveIndicatorExpandedPadding(
+ getLayoutDirection() == LAYOUT_DIRECTION_RTL
+ ? activeIndicatorExpandedEndPadding : activeIndicatorExpandedStartPadding,
+ activeIndicatorAttributes.getDimensionPixelOffset(
+ R.styleable.NavigationBarActiveIndicator_expandedActiveIndicatorPaddingTop,
+ 0),
+ getLayoutDirection() == LAYOUT_DIRECTION_RTL
+ ? activeIndicatorExpandedStartPadding : activeIndicatorExpandedEndPadding,
+ activeIndicatorAttributes.getDimensionPixelOffset(
+ R.styleable.NavigationBarActiveIndicator_expandedActiveIndicatorPaddingBottom,
+ 0));
+
ColorStateList itemActiveIndicatorColor =
MaterialResources.getColorStateList(
context,
@@ -708,6 +736,34 @@ private void setMeasureBottomPaddingFromLabelBaseline(boolean measurePaddingFrom
menuView.setMeasurePaddingFromLabelBaseline(measurePaddingFromBaseline);
}
+ /**
+ * Sets whether or not the label text should scale with the system font size.
+ */
+ public void setLabelFontScalingEnabled(boolean labelFontScalingEnabled) {
+ menuView.setLabelFontScalingEnabled(labelFontScalingEnabled);
+ }
+
+ /**
+ * Returns whether or not the label text should scale with the system font size.
+ */
+ public boolean getScaleLabelTextWithFont() {
+ return menuView.getScaleLabelTextWithFont();
+ }
+
+ /**
+ * Set the max lines limit for the label text.
+ */
+ public void setLabelMaxLines(int labelMaxLines) {
+ menuView.setLabelMaxLines(labelMaxLines);
+ }
+
+ /**
+ * Returns the max lines limit for the label text.
+ */
+ public int getLabelMaxLines(int labelMaxLines) {
+ return menuView.getLabelMaxLines();
+ }
+
/**
* Set the distance between the active indicator container and the item's label.
*/
@@ -825,7 +881,7 @@ public void setItemActiveIndicatorMarginHorizontal(@Px int horizontalMargin) {
* @see #getItemGravity()
*/
public void setItemGravity(@ItemGravity int itemGravity) {
- if (menuView.getItemIconGravity() != itemGravity) {
+ if (menuView.getItemGravity() != itemGravity) {
menuView.setItemGravity(itemGravity);
presenter.updateMenuView(false);
}
@@ -907,6 +963,19 @@ public void setItemActiveIndicatorExpandedMarginHorizontal(@Px int horizontalMar
menuView.setItemActiveIndicatorExpandedMarginHorizontal(horizontalMargin);
}
+ /**
+ * Set the padding of the expanded active indicator wrapping the content.
+ *
+ * @param paddingLeft The left padding, in pixels.
+ * @param paddingTop The top padding, in pixels.
+ * @param paddingRight The right padding, in pixels.
+ * @param paddingBottom The bottom padding, in pixels.
+ */
+ public void setItemActiveIndicatorExpandedPadding(
+ @Px int paddingLeft, @Px int paddingTop, @Px int paddingRight, @Px int paddingBottom) {
+ menuView.setItemActiveIndicatorExpandedPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
+ }
+
/**
* Get the {@link ShapeAppearanceModel} of the active indicator drawable.
*
diff --git a/lib/java/com/google/android/material/navigation/NavigationView.java b/lib/java/com/google/android/material/navigation/NavigationView.java
index 89a83328ad3..aaaaa23022c 100644
--- a/lib/java/com/google/android/material/navigation/NavigationView.java
+++ b/lib/java/com/google/android/material/navigation/NavigationView.java
@@ -66,7 +66,6 @@
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
import androidx.annotation.VisibleForTesting;
-import androidx.core.content.ContextCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.customview.view.AbsSavedState;
import androidx.drawerlayout.widget.DrawerLayout;
@@ -722,7 +721,7 @@ public Drawable getItemBackground() {
* @attr ref R.styleable#NavigationView_itemBackground
*/
public void setItemBackgroundResource(@DrawableRes int resId) {
- setItemBackground(ContextCompat.getDrawable(getContext(), resId));
+ setItemBackground(getContext().getDrawable(resId));
}
/**
diff --git a/lib/java/com/google/android/material/navigation/res-public/values/public.xml b/lib/java/com/google/android/material/navigation/res-public/values/public.xml
index 26219a99858..3e897ec0001 100644
--- a/lib/java/com/google/android/material/navigation/res-public/values/public.xml
+++ b/lib/java/com/google/android/material/navigation/res-public/values/public.xml
@@ -66,5 +66,13 @@
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/navigation/res/layout/m3_navigation_menu_divider.xml b/lib/java/com/google/android/material/navigation/res/layout/m3_navigation_menu_divider.xml
new file mode 100644
index 00000000000..0eab193e9fe
--- /dev/null
+++ b/lib/java/com/google/android/material/navigation/res/layout/m3_navigation_menu_divider.xml
@@ -0,0 +1,25 @@
+
+
+
diff --git a/lib/java/com/google/android/material/navigation/res/layout/m3_navigation_menu_subheader.xml b/lib/java/com/google/android/material/navigation/res/layout/m3_navigation_menu_subheader.xml
index 12a62e0e2f3..f797a7644e4 100644
--- a/lib/java/com/google/android/material/navigation/res/layout/m3_navigation_menu_subheader.xml
+++ b/lib/java/com/google/android/material/navigation/res/layout/m3_navigation_menu_subheader.xml
@@ -20,7 +20,7 @@
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/m3_navigation_subheader_horizontal_padding"
android:paddingVertical="@dimen/m3_navigation_subheader_vertical_padding"
- android:layout_marginStart="@dimen/m3_navigation_subheader_horizontal_margin"
+ android:layout_marginStart="@dimen/m3_navigation_content_horizontal_margin"
android:layout_marginTop="@dimen/m3_navigation_subheader_top_margin"
android:gravity="center_vertical|start"
android:ellipsize="end"
diff --git a/lib/java/com/google/android/material/navigation/res/values/attrs.xml b/lib/java/com/google/android/material/navigation/res/values/attrs.xml
index 1080bd0c92a..d5208181a9a 100644
--- a/lib/java/com/google/android/material/navigation/res/values/attrs.xml
+++ b/lib/java/com/google/android/material/navigation/res/values/attrs.xml
@@ -118,6 +118,12 @@
+
+
+
+
+
+
@@ -145,6 +151,18 @@
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/navigation/res/values/dimens.xml b/lib/java/com/google/android/material/navigation/res/values/dimens.xml
index 50e91b6d3c3..2d901c08fcf 100644
--- a/lib/java/com/google/android/material/navigation/res/values/dimens.xml
+++ b/lib/java/com/google/android/material/navigation/res/values/dimens.xml
@@ -50,7 +50,9 @@
16dp
8dp
12dp
- 20dp
+ 20dp
+ 8dp
+ 3dp
12sp
14sp
diff --git a/lib/java/com/google/android/material/navigation/res/values/tokens.xml b/lib/java/com/google/android/material/navigation/res/values/tokens.xml
index 0f1f887e415..91921dc3b67 100644
--- a/lib/java/com/google/android/material/navigation/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/navigation/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/navigationrail/NavigationRailMenuView.java b/lib/java/com/google/android/material/navigationrail/NavigationRailMenuView.java
index f7d2df73ee7..3098bf38fb4 100644
--- a/lib/java/com/google/android/material/navigationrail/NavigationRailMenuView.java
+++ b/lib/java/com/google/android/material/navigationrail/NavigationRailMenuView.java
@@ -32,7 +32,6 @@
import androidx.annotation.RestrictTo;
import com.google.android.material.navigation.NavigationBarItemView;
import com.google.android.material.navigation.NavigationBarMenuView;
-import com.google.android.material.navigation.NavigationBarSubheaderView;
/** @hide For internal use only. */
@RestrictTo(LIBRARY_GROUP)
@@ -140,7 +139,7 @@ private int measureSharedChildHeights(
int totalHeight = 0;
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
- if (child instanceof NavigationBarSubheaderView) {
+ if (!(child instanceof NavigationBarItemView)) {
int subheaderHeight = measureChildHeight(child, widthMeasureSpec, subheaderHeightSpec);
maxHeight -= subheaderHeight;
totalHeight += subheaderHeight;
@@ -175,8 +174,8 @@ private int measureSharedChildHeights(
}
private int measureChildHeight(View child, int widthMeasureSpec, int heightMeasureSpec) {
+ child.measure(widthMeasureSpec, heightMeasureSpec);
if (child.getVisibility() != GONE) {
- child.measure(widthMeasureSpec, heightMeasureSpec);
return child.getMeasuredHeight();
}
diff --git a/lib/java/com/google/android/material/navigationrail/NavigationRailView.java b/lib/java/com/google/android/material/navigationrail/NavigationRailView.java
index 7107eec6d62..e32ff9f96f1 100644
--- a/lib/java/com/google/android/material/navigationrail/NavigationRailView.java
+++ b/lib/java/com/google/android/material/navigationrail/NavigationRailView.java
@@ -26,11 +26,13 @@
import static java.lang.Math.min;
import android.animation.TimeInterpolator;
+import android.annotation.SuppressLint;
import android.content.Context;
import androidx.appcompat.widget.TintTypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.PathInterpolator;
@@ -52,6 +54,7 @@
import com.google.android.material.internal.ThemeEnforcement;
import com.google.android.material.internal.ViewUtils;
import com.google.android.material.internal.ViewUtils.RelativePadding;
+import com.google.android.material.navigation.NavigationBarDividerView;
import com.google.android.material.navigation.NavigationBarItemView;
import com.google.android.material.navigation.NavigationBarView;
import com.google.android.material.resources.MaterialResources;
@@ -133,6 +136,7 @@ public class NavigationRailView extends NavigationBarView {
private final int minExpandedWidth;
private final int maxExpandedWidth;
private final boolean scrollingEnabled;
+ private boolean submenuDividersEnabled;
@Nullable private View headerView;
@Nullable private Boolean paddingTopSystemWindowInsets = null;
@Nullable private Boolean paddingBottomSystemWindowInsets = null;
@@ -169,22 +173,10 @@ public NavigationRailView(
// Ensure we are using the correctly themed context rather than the context that was passed in.
context = getContext();
- minExpandedWidth =
- getContext()
- .getResources()
- .getDimensionPixelSize(R.dimen.m3_navigation_rail_min_expanded_width);
- maxExpandedWidth =
- getContext()
- .getResources()
- .getDimensionPixelSize(R.dimen.m3_navigation_rail_max_expanded_width);
expandedItemSpacing =
getContext()
.getResources()
.getDimensionPixelSize(R.dimen.m3_navigation_rail_expanded_item_spacing);
- expandedItemMinHeight =
- getContext()
- .getResources()
- .getDimensionPixelSize(R.dimen.m3_navigation_rail_expanded_item_min_height);
expandedItemGravity = ITEM_GRAVITY_START_CENTER;
expandedIconGravity = ITEM_ICON_GRAVITY_START;
@@ -202,6 +194,7 @@ public NavigationRailView(
getResources().getDimensionPixelSize(R.dimen.mtrl_navigation_rail_margin));
scrollingEnabled =
attributes.getBoolean(R.styleable.NavigationRailView_scrollingEnabled, false);
+ setSubmenuDividersEnabled(attributes.getBoolean(R.styleable.NavigationRailView_submenuDividersEnabled, false));
addContentContainer();
@@ -213,11 +206,31 @@ public NavigationRailView(
setMenuGravity(
attributes.getInt(R.styleable.NavigationRailView_menuGravity, DEFAULT_MENU_GRAVITY));
- if (attributes.hasValue(R.styleable.NavigationRailView_itemMinHeight)) {
- setCollapsedItemMinimumHeight(
- attributes.getDimensionPixelSize(
- R.styleable.NavigationRailView_itemMinHeight, NO_ITEM_MINIMUM_HEIGHT));
+ int collapsedItemMinHeight = attributes.getDimensionPixelSize(
+ R.styleable.NavigationRailView_itemMinHeight, NO_ITEM_MINIMUM_HEIGHT);
+ int expandedItemMinHeight = attributes.getDimensionPixelSize(
+ R.styleable.NavigationRailView_itemMinHeight, NO_ITEM_MINIMUM_HEIGHT);
+
+ if (attributes.hasValue(R.styleable.NavigationRailView_collapsedItemMinHeight)) {
+ collapsedItemMinHeight = attributes.getDimensionPixelSize(
+ R.styleable.NavigationRailView_collapsedItemMinHeight, NO_ITEM_MINIMUM_HEIGHT);
+ }
+ if (attributes.hasValue(R.styleable.NavigationRailView_expandedItemMinHeight)) {
+ expandedItemMinHeight = attributes.getDimensionPixelSize(
+ R.styleable.NavigationRailView_expandedItemMinHeight, NO_ITEM_MINIMUM_HEIGHT);
}
+ setCollapsedItemMinimumHeight(collapsedItemMinHeight);
+ setExpandedItemMinimumHeight(expandedItemMinHeight);
+ minExpandedWidth = attributes.getDimensionPixelSize(
+ R.styleable.NavigationRailView_expandedMinWidth,
+ context
+ .getResources()
+ .getDimensionPixelSize(R.dimen.m3_navigation_rail_min_expanded_width));
+ maxExpandedWidth = attributes.getDimensionPixelSize(
+ R.styleable.NavigationRailView_expandedMaxWidth,
+ context
+ .getResources()
+ .getDimensionPixelSize(R.dimen.m3_navigation_rail_max_expanded_width));
if (attributes.hasValue(R.styleable.NavigationRailView_paddingTopSystemWindowInsets)) {
paddingTopSystemWindowInsets =
@@ -406,8 +419,9 @@ public WindowInsetsCompat onApplyWindowInsets(
@NonNull WindowInsetsCompat insets,
@NonNull RelativePadding initialPadding) {
// Apply the top, bottom, and start padding for a start edge aligned
- // NavigationRailView to dodge the system status and navigation bars
+ // NavigationRailView to dodge the system status/navigation bars and display cutouts
Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ Insets displayCutoutInsets = insets.getInsets(WindowInsetsCompat.Type.displayCutout());
if (shouldApplyWindowInsetPadding(paddingTopSystemWindowInsets)) {
initialPadding.top += systemBarInsets.top;
}
@@ -415,8 +429,11 @@ public WindowInsetsCompat onApplyWindowInsets(
initialPadding.bottom += systemBarInsets.bottom;
}
if (shouldApplyWindowInsetPadding(paddingStartSystemWindowInsets)) {
- initialPadding.start +=
- ViewUtils.isLayoutRtl(view) ? systemBarInsets.right : systemBarInsets.left;
+ if (ViewUtils.isLayoutRtl(view)) {
+ initialPadding.start += max(systemBarInsets.right, displayCutoutInsets.right);
+ } else {
+ initialPadding.start += max(systemBarInsets.left, displayCutoutInsets.left);
+ }
}
initialPadding.applyToView(view);
return insets;
@@ -439,7 +456,7 @@ private int getMaxChildWidth() {
int maxChildWidth = 0;
for (int i = 0; i < childCount; i++) {
View child = getNavigationRailMenuView().getChildAt(i);
- if (child.getVisibility() != GONE) {
+ if (child.getVisibility() != GONE && !(child instanceof NavigationBarDividerView)) {
maxChildWidth = max(maxChildWidth, child.getMeasuredWidth());
}
}
@@ -457,6 +474,10 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
}
// Measure properly with the max child width
minWidthSpec = makeExpandedWidthMeasureSpec(widthMeasureSpec, getMaxChildWidth());
+ // If the active indicator is match_parent, it should be notified to update its width
+ if (getItemActiveIndicatorExpandedWidth() == MATCH_PARENT) {
+ getNavigationRailMenuView().updateActiveIndicator(MeasureSpec.getSize(minWidthSpec));
+ }
}
super.onMeasure(minWidthSpec, heightMeasureSpec);
// If the content container is measured to be less than the measured height of the nav rail,
@@ -568,6 +589,50 @@ public void setCollapsedItemMinimumHeight(@Px int minHeight) {
}
}
+ /**
+ * Gets the minimum height of a navigation rail menu item when the navigation rail is collapsed.
+ */
+ public int getCollapsedItemMinimumHeight() {
+ return collapsedItemMinHeight;
+ }
+
+ /**
+ * Sets the minimum height of a navigation rail menu item when the navigation rail is expanded.
+ *
+ * @param minHeight the min height of the item when the nav rail is collapsed
+ */
+ public void setExpandedItemMinimumHeight(@Px int minHeight) {
+ expandedItemMinHeight = minHeight;
+ if (expanded) {
+ ((NavigationRailMenuView) getMenuView()).setItemMinimumHeight(minHeight);
+ }
+ }
+
+ /**
+ * Gets the minimum height of a navigation rail menu item when the navigation rail is expanded.
+ */
+ public int getExpandedItemMinimumHeight() {
+ return expandedItemMinHeight;
+ }
+
+ /**
+ * Set whether or not to enable the dividers which go between each subgroup in the menu.
+ */
+ public void setSubmenuDividersEnabled(boolean submenuDividersEnabled) {
+ if (this.submenuDividersEnabled == submenuDividersEnabled) {
+ return;
+ }
+ this.submenuDividersEnabled = submenuDividersEnabled;
+ getNavigationRailMenuView().setSubmenuDividersEnabled(submenuDividersEnabled);
+ }
+
+ /**
+ * Get whether or not to enable the dividers which go between each subgroup in the menu.
+ */
+ public boolean getSubmenuDividersEnabled() {
+ return submenuDividersEnabled;
+ }
+
/**
* Set the padding in between the navigation rail menu items.
*/
@@ -679,4 +744,12 @@ private void addContentContainer() {
public boolean shouldAddMenuView() {
return true;
}
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ super.onTouchEvent(event);
+ // Consume all events to avoid views under the NavigationRailView from receiving touch events.
+ return true;
+ }
}
diff --git a/lib/java/com/google/android/material/navigationrail/res-public/values/public.xml b/lib/java/com/google/android/material/navigationrail/res-public/values/public.xml
index e592eb6b94d..c868d2d2803 100644
--- a/lib/java/com/google/android/material/navigationrail/res-public/values/public.xml
+++ b/lib/java/com/google/android/material/navigationrail/res-public/values/public.xml
@@ -18,12 +18,16 @@
+
+
+
+
diff --git a/lib/java/com/google/android/material/navigationrail/res/color/m3_navigation_rail_item_with_indicator_icon_tint.xml b/lib/java/com/google/android/material/navigationrail/res/color/m3_navigation_rail_item_with_indicator_icon_tint.xml
index d5fd948cb8c..c43fcc06fd6 100644
--- a/lib/java/com/google/android/material/navigationrail/res/color/m3_navigation_rail_item_with_indicator_icon_tint.xml
+++ b/lib/java/com/google/android/material/navigationrail/res/color/m3_navigation_rail_item_with_indicator_icon_tint.xml
@@ -15,6 +15,6 @@
~ limitations under the License.
-->
-
-
+
+
diff --git a/lib/java/com/google/android/material/navigationrail/res/color/m3_navigation_rail_item_with_indicator_label_tint.xml b/lib/java/com/google/android/material/navigationrail/res/color/m3_navigation_rail_item_with_indicator_label_tint.xml
index 1018aaa840c..9c054f2cff4 100644
--- a/lib/java/com/google/android/material/navigationrail/res/color/m3_navigation_rail_item_with_indicator_label_tint.xml
+++ b/lib/java/com/google/android/material/navigationrail/res/color/m3_navigation_rail_item_with_indicator_label_tint.xml
@@ -15,6 +15,6 @@
~ limitations under the License.
-->
-
-
+
+
diff --git a/lib/java/com/google/android/material/navigationrail/res/color/m3_navigation_rail_ripple_color_selector.xml b/lib/java/com/google/android/material/navigationrail/res/color/m3_navigation_rail_ripple_color_selector.xml
index a64bfbc252a..58005b8c3f8 100644
--- a/lib/java/com/google/android/material/navigationrail/res/color/m3_navigation_rail_ripple_color_selector.xml
+++ b/lib/java/com/google/android/material/navigationrail/res/color/m3_navigation_rail_ripple_color_selector.xml
@@ -17,14 +17,26 @@
-
-
-
+
+
+
-
-
-
+
+
+
diff --git a/lib/java/com/google/android/material/navigationrail/res/color/m3expressive_nav_rail_item_icon_tint.xml b/lib/java/com/google/android/material/navigationrail/res/color/m3expressive_nav_rail_item_icon_tint.xml
new file mode 100644
index 00000000000..78bef7837a0
--- /dev/null
+++ b/lib/java/com/google/android/material/navigationrail/res/color/m3expressive_nav_rail_item_icon_tint.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/navigationrail/res/color/m3expressive_nav_rail_item_label_tint.xml b/lib/java/com/google/android/material/navigationrail/res/color/m3expressive_nav_rail_item_label_tint.xml
new file mode 100644
index 00000000000..0c6b43684b1
--- /dev/null
+++ b/lib/java/com/google/android/material/navigationrail/res/color/m3expressive_nav_rail_item_label_tint.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/navigationrail/res/color/m3expressive_nav_rail_item_ripple_tint.xml b/lib/java/com/google/android/material/navigationrail/res/color/m3expressive_nav_rail_item_ripple_tint.xml
new file mode 100644
index 00000000000..8d6ba766149
--- /dev/null
+++ b/lib/java/com/google/android/material/navigationrail/res/color/m3expressive_nav_rail_item_ripple_tint.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/navigationrail/res/layout/mtrl_navigation_rail_item.xml b/lib/java/com/google/android/material/navigationrail/res/layout/mtrl_navigation_rail_item.xml
index a407643f3bc..30a44a2c8de 100644
--- a/lib/java/com/google/android/material/navigationrail/res/layout/mtrl_navigation_rail_item.xml
+++ b/lib/java/com/google/android/material/navigationrail/res/layout/mtrl_navigation_rail_item.xml
@@ -72,7 +72,7 @@
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:ellipsize="end"
- android:maxLines="1"
+ android:gravity="center_vertical"
android:paddingStart="@dimen/m3_navigation_rail_label_padding_horizontal"
android:paddingEnd="@dimen/m3_navigation_rail_label_padding_horizontal"
android:textSize="@dimen/mtrl_navigation_rail_text_size" />
@@ -81,8 +81,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
+ android:gravity="center_vertical"
android:ellipsize="end"
- android:maxLines="1"
android:textSize="@dimen/mtrl_navigation_rail_active_text_size"
android:paddingStart="@dimen/m3_navigation_rail_label_padding_horizontal"
android:paddingEnd="@dimen/m3_navigation_rail_label_padding_horizontal"
diff --git a/lib/java/com/google/android/material/navigationrail/res/values/attrs.xml b/lib/java/com/google/android/material/navigationrail/res/values/attrs.xml
index ef396190adf..82e83ee8c47 100644
--- a/lib/java/com/google/android/material/navigationrail/res/values/attrs.xml
+++ b/lib/java/com/google/android/material/navigationrail/res/values/attrs.xml
@@ -19,9 +19,19 @@
-
+
+
+
+
+
+
+
+
+
@@ -61,6 +71,8 @@
+
+
diff --git a/lib/java/com/google/android/material/navigationrail/res/values/dimens.xml b/lib/java/com/google/android/material/navigationrail/res/values/dimens.xml
index 8e158f81ef9..3c21836c2ee 100644
--- a/lib/java/com/google/android/material/navigationrail/res/values/dimens.xml
+++ b/lib/java/com/google/android/material/navigationrail/res/values/dimens.xml
@@ -26,16 +26,16 @@
14dp
16dp
- @dimen/m3_comp_navigation_rail_container_elevation
- @dimen/m3_comp_navigation_rail_icon_size
- @dimen/m3_comp_navigation_rail_container_width
+ @dimen/m3_comp_nav_rail_collapsed_container_elevation
+ @dimen/m3_comp_nav_rail_item_icon_size
+ @dimen/m3_comp_nav_rail_collapsed_narrow_container_width
60dp
4dp
12dp
- 20dp
12dp
- @dimen/m3_comp_navigation_rail_active_indicator_width
- @dimen/m3_comp_navigation_rail_active_indicator_height
+ 20dp
+ @dimen/m3_comp_nav_rail_item_vertical_active_indicator_width
+ @dimen/m3_comp_nav_rail_item_vertical_active_indicator_height
4dp
2dp
4dp
@@ -47,6 +47,14 @@
220dp
360dp
0dp
- 56dp
+
+ @dimen/m3_comp_nav_rail_item_container_height
+ @dimen/m3_comp_nav_rail_collapsed_container_width
+ @dimen/m3_comp_nav_rail_collapsed_top_space
+ @dimen/m3_comp_nav_rail_item_header_space_minimum
+ @dimen/m3_comp_nav_rail_item_container_vertical_space
+ @dimen/m3_comp_nav_rail_collapsed_item_vertical_space
+
+ 20dp
diff --git a/lib/java/com/google/android/material/navigationrail/res/values/styles.xml b/lib/java/com/google/android/material/navigationrail/res/values/styles.xml
index b93b31a3268..3cf74f3ec11 100644
--- a/lib/java/com/google/android/material/navigationrail/res/values/styles.xml
+++ b/lib/java/com/google/android/material/navigationrail/res/values/styles.xml
@@ -16,6 +16,99 @@
-->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -68,46 +161,4 @@
-
-
-
-
-
-
-
-
-
diff --git a/lib/java/com/google/android/material/navigationrail/res/values/tokens.xml b/lib/java/com/google/android/material/navigationrail/res/values/tokens.xml
index 7cfc86d3c34..e9ac547f9fa 100644
--- a/lib/java/com/google/android/material/navigationrail/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/navigationrail/res/values/tokens.xml
@@ -15,40 +15,66 @@
~ limitations under the License.
-->
-
+
-
-
- ?attr/colorSurface
-
- 80dp
- @dimen/m3_sys_elevation_level0
-
- ?attr/textAppearanceLabelMedium
- ?attr/colorOnSurface
- ?attr/colorOnSurfaceVariant
-
- 24dp
- ?attr/colorOnSecondaryContainer
- ?attr/colorOnSurfaceVariant
-
- ?attr/colorSecondaryContainer
- 32dp
-
- 56dp
-
- ?attr/colorOnSurface
- ?attr/colorOnSurface
- @dimen/m3_sys_state_hover_state_layer_opacity
-
- ?attr/colorOnSurface
- ?attr/colorOnSurface
- @dimen/m3_sys_state_focus_state_layer_opacity
-
- ?attr/colorOnSurface
- ?attr/colorOnSurface
- @dimen/m3_sys_state_pressed_state_layer_opacity
+
+
+ ?attr/colorSecondaryContainer
+ ?attr/colorSecondary
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSecondaryContainer
+ ?attr/colorOnSurfaceVariant
+
+ ?attr/colorOnSecondaryContainer
+ @dimen/m3_sys_state_hover_state_layer_opacity
+ ?attr/colorOnSecondaryContainer
+
+ ?attr/colorOnSecondaryContainer
+ @dimen/m3_sys_state_focus_state_layer_opacity
+ ?attr/colorOnSecondaryContainer
+
+ ?attr/colorOnSecondaryContainer
+ @dimen/m3_sys_state_pressed_state_layer_opacity
+ ?attr/colorOnSecondaryContainer
+
+
+ 96dp
+ 80dp
+ @dimen/m3_sys_elevation_level0
+
+ ?attr/colorSurface
+ 4dp
+ 44dp
+
+
+ 220dp
+ 360dp
+
+
+
+ 24dp
+
+ 64dp
+ 56dp
+ 6dp
+ 40dp
+
+
+ 32dp
+ 56dp
+ ?attr/textAppearanceLabelMedium
+ 4dp
+ 16dp
+ 16dp
+
+
+ ?attr/textAppearanceLabelLarge
+ 56dp
+ 16dp
+ 16dp
+ 8dp
diff --git a/lib/java/com/google/android/material/progressindicator/BaseProgressIndicator.java b/lib/java/com/google/android/material/progressindicator/BaseProgressIndicator.java
index 158686115b3..a2481e11bc5 100644
--- a/lib/java/com/google/android/material/progressindicator/BaseProgressIndicator.java
+++ b/lib/java/com/google/android/material/progressindicator/BaseProgressIndicator.java
@@ -29,6 +29,7 @@
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ProgressBar;
import androidx.annotation.AttrRes;
@@ -471,10 +472,10 @@ && getWindowVisibility() == View.VISIBLE
* This is necessary as before API 24, it is not guaranteed that Views will ever be notified
* about their parent changing. Thus, we don't have a proper point to hook in and re-check {@link
* #isShown()} on parent changes that result from {@link
- * android.view.ViewGroup#attachViewToParent(View, int, LayoutParams)}, which *can* change our
- * effective visibility. So this method errs on the side of assuming visibility unless we can
- * conclusively prove otherwise (but may result in some false positives, if this view ends up
- * being attached to a non-visible hierarchy after being detached in a visible state).
+ * android.view.ViewGroup#attachViewToParent(View, int, ViewGroup.LayoutParams)}, which *can*
+ * change our effective visibility. So this method errs on the side of assuming visibility unless
+ * we can conclusively prove otherwise (but may result in some false positives, if this view
+ * ends up being attached to a non-visible hierarchy after being detached in a visible state).
*/
boolean isEffectivelyVisible() {
View current = this;
@@ -588,7 +589,11 @@ public int[] getIndicatorColor() {
public void setIndicatorColor(@ColorInt int... indicatorColors) {
if (indicatorColors.length == 0) {
// Uses theme primary color for indicator by default. Indicator color cannot be empty.
- indicatorColors = new int[] {MaterialColors.getColor(getContext(), R.attr.colorPrimary, -1)};
+ indicatorColors =
+ new int[] {
+ MaterialColors.getColor(
+ getContext(), androidx.appcompat.R.attr.colorPrimary, -1)
+ };
}
if (!Arrays.equals(getIndicatorColor(), indicatorColors)) {
spec.indicatorColors = indicatorColors;
@@ -646,7 +651,36 @@ public int getTrackCornerRadius() {
*/
public void setTrackCornerRadius(@Px int trackCornerRadius) {
if (spec.trackCornerRadius != trackCornerRadius) {
- spec.trackCornerRadius = Math.round(min(trackCornerRadius, spec.trackThickness / 2f));
+ spec.trackCornerRadius = min(trackCornerRadius, spec.trackThickness / 2);
+ spec.useRelativeTrackCornerRadius = false;
+ invalidate();
+ }
+ }
+
+ /**
+ * Returns the relative radius of the rounded corner for the indicator and track in pixels.
+ *
+ * @see #setTrackCornerRadiusFraction(float)
+ * @attr ref
+ * com.google.android.material.progressindicator.R.styleable#BaseProgressIndicator_trackCornerRadius
+ */
+ public float getTrackCornerRadiusFraction() {
+ return spec.trackCornerRadiusFraction;
+ }
+
+ /**
+ * Sets the radius of the rounded corner for the indicator and track in fraction of track
+ * thickness.
+ *
+ * @param fraction The fraction of corner radius to track thickness.
+ * @see #getTrackCornerRadiusFraction()
+ * @attr ref
+ * com.google.android.material.progressindicator.R.styleable#BaseProgressIndicator_trackCornerRadius
+ */
+ public void setTrackCornerRadiusFraction(@FloatRange(from = 0f, to = 0.5f) float fraction) {
+ if (spec.trackCornerRadiusFraction != fraction) {
+ spec.trackCornerRadiusFraction = min(fraction, 0.5f);
+ spec.useRelativeTrackCornerRadius = true;
invalidate();
}
}
diff --git a/lib/java/com/google/android/material/progressindicator/BaseProgressIndicatorSpec.java b/lib/java/com/google/android/material/progressindicator/BaseProgressIndicatorSpec.java
index 7c17a85285b..d633a7e2416 100644
--- a/lib/java/com/google/android/material/progressindicator/BaseProgressIndicatorSpec.java
+++ b/lib/java/com/google/android/material/progressindicator/BaseProgressIndicatorSpec.java
@@ -54,6 +54,18 @@ public abstract class BaseProgressIndicatorSpec {
*/
@Px public int trackCornerRadius;
+ /**
+ * The fraction of the track thickness to be used as the corner radius. And the stroke ROUND cap
+ * is used to prevent artifacts like (b/319309456), when 0.5f is specified.
+ */
+ public float trackCornerRadiusFraction;
+
+ /**
+ * When this is true, the {#link trackCornerRadiusFraction} takes effect. Otherwise, the {@link
+ * trackCornerRadius} takes effect.
+ */
+ public boolean useRelativeTrackCornerRadius;
+
/**
* The color array used in the indicator. In determinate mode, only the first item will be used.
*/
@@ -112,11 +124,21 @@ protected BaseProgressIndicatorSpec(
trackThickness =
getDimensionPixelSize(
context, a, R.styleable.BaseProgressIndicator_trackThickness, defaultIndicatorSize);
- trackCornerRadius =
- min(
- getDimensionPixelSize(
- context, a, R.styleable.BaseProgressIndicator_trackCornerRadius, 0),
- Math.round(trackThickness / 2f));
+ TypedValue trackCornerRadiusValue =
+ a.peekValue(R.styleable.BaseProgressIndicator_trackCornerRadius);
+ if (trackCornerRadiusValue != null) {
+ if (trackCornerRadiusValue.type == TypedValue.TYPE_DIMENSION) {
+ trackCornerRadius =
+ min(
+ TypedValue.complexToDimensionPixelSize(
+ trackCornerRadiusValue.data, a.getResources().getDisplayMetrics()),
+ trackThickness / 2);
+ useRelativeTrackCornerRadius = false;
+ } else if (trackCornerRadiusValue.type == TypedValue.TYPE_FRACTION) {
+ trackCornerRadiusFraction = min(trackCornerRadiusValue.getFraction(1.0f, 1.0f), 0.5f);
+ useRelativeTrackCornerRadius = true;
+ }
+ }
showAnimationBehavior =
a.getInt(
R.styleable.BaseProgressIndicator_showAnimationBehavior,
@@ -160,7 +182,10 @@ protected BaseProgressIndicatorSpec(
private void loadIndicatorColors(@NonNull Context context, @NonNull TypedArray typedArray) {
if (!typedArray.hasValue(R.styleable.BaseProgressIndicator_indicatorColor)) {
// Uses theme primary color for indicator if not provided in the attribute set.
- indicatorColors = new int[] {MaterialColors.getColor(context, R.attr.colorPrimary, -1)};
+ indicatorColors =
+ new int[] {
+ MaterialColors.getColor(context, androidx.appcompat.R.attr.colorPrimary, -1)
+ };
return;
}
@@ -222,6 +247,27 @@ public boolean hasWavyEffect(boolean isDeterminate) {
&& ((!isDeterminate && wavelengthIndeterminate > 0)
|| (isDeterminate && wavelengthDeterminate > 0));
}
+
+ /**
+ * Returns the track corner radius in pixels.
+ *
+ *
If {@link #useRelativeTrackCornerRadius} is true, the track corner radius is calculated
+ * using the track thickness and the track corner radius fraction. Otherwise, the track corner
+ * radius is returned directly.
+ */
+ public int getTrackCornerRadiusInPx() {
+ return useRelativeTrackCornerRadius
+ ? (int) (trackThickness * trackCornerRadiusFraction)
+ : trackCornerRadius;
+ }
+
+ /**
+ * Returns true if the stroke ROUND cap should be used to prevent artifacts like (b/319309456),
+ * when fully rounded corners are specified.
+ */
+ public boolean useStrokeCap() {
+ return useRelativeTrackCornerRadius && trackCornerRadiusFraction == 0.5f;
+ }
@CallSuper
void validateSpec() {
diff --git a/lib/java/com/google/android/material/progressindicator/CircularDrawingDelegate.java b/lib/java/com/google/android/material/progressindicator/CircularDrawingDelegate.java
index 737fe4e3b3d..6a2e16d897c 100644
--- a/lib/java/com/google/android/material/progressindicator/CircularDrawingDelegate.java
+++ b/lib/java/com/google/android/material/progressindicator/CircularDrawingDelegate.java
@@ -58,8 +58,6 @@ final class CircularDrawingDelegate extends DrawingDelegate 0) {
+ if (!spec.useStrokeCap() && displayedCornerRadius > 0) {
paint.setStyle(Style.FILL);
drawRoundedBlock(canvas, paint, endPoints.first, blockWidth, displayedTrackThickness);
drawRoundedBlock(canvas, paint, endPoints.second, blockWidth, displayedTrackThickness);
diff --git a/lib/java/com/google/android/material/progressindicator/DrawableWithAnimatedVisibilityChange.java b/lib/java/com/google/android/material/progressindicator/DrawableWithAnimatedVisibilityChange.java
index 70f06bc689d..42b2f959754 100644
--- a/lib/java/com/google/android/material/progressindicator/DrawableWithAnimatedVisibilityChange.java
+++ b/lib/java/com/google/android/material/progressindicator/DrawableWithAnimatedVisibilityChange.java
@@ -25,6 +25,7 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
import android.util.Property;
import androidx.annotation.FloatRange;
import androidx.annotation.IntRange;
@@ -467,7 +468,7 @@ float getPhaseFraction() {
? baseSpec.wavelengthDeterminate
: baseSpec.wavelengthIndeterminate;
int cycleInMs = (int) (1000f * wavelength / baseSpec.waveSpeed * durationScale);
- phaseFraction = (float) (System.currentTimeMillis() % cycleInMs) / cycleInMs;
+ phaseFraction = (float) (SystemClock.uptimeMillis() % cycleInMs) / cycleInMs;
if (phaseFraction < 0f) {
phaseFraction = (phaseFraction % 1) + 1f;
}
diff --git a/lib/java/com/google/android/material/progressindicator/IndeterminateDrawable.java b/lib/java/com/google/android/material/progressindicator/IndeterminateDrawable.java
index 827f47e786e..531b4cf1480 100644
--- a/lib/java/com/google/android/material/progressindicator/IndeterminateDrawable.java
+++ b/lib/java/com/google/android/material/progressindicator/IndeterminateDrawable.java
@@ -31,7 +31,6 @@
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.annotation.VisibleForTesting;
-import androidx.core.graphics.drawable.DrawableCompat;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import com.google.android.material.progressindicator.DrawingDelegate.ActiveIndicator;
@@ -189,7 +188,7 @@ public void draw(@NonNull Canvas canvas) {
if (isSystemAnimatorDisabled() && staticDummyDrawable != null) {
staticDummyDrawable.setBounds(getBounds());
- DrawableCompat.setTint(staticDummyDrawable, baseSpec.indicatorColors[0]);
+ staticDummyDrawable.setTint(baseSpec.indicatorColors[0]);
staticDummyDrawable.draw(canvas);
return;
}
diff --git a/lib/java/com/google/android/material/progressindicator/LinearDrawingDelegate.java b/lib/java/com/google/android/material/progressindicator/LinearDrawingDelegate.java
index b03a5091b08..9f604a2250b 100644
--- a/lib/java/com/google/android/material/progressindicator/LinearDrawingDelegate.java
+++ b/lib/java/com/google/android/material/progressindicator/LinearDrawingDelegate.java
@@ -51,10 +51,10 @@ final class LinearDrawingDelegate extends DrawingDelegate= endBlockCenterX) {
- // Draws the start rounded block clipped by the end rounded block.
+
+ if (startPx == 0
+ && endBlockCenterX + endCornerRadius < startBlockCenterX + startCornerRadius) {
+ drawRoundedBlock(
+ canvas,
+ paint,
+ endPoints.first,
+ startBlockWidth,
+ displayedTrackThickness,
+ startCornerRadius,
+ endPoints.second,
+ endBlockWidth,
+ displayedTrackThickness,
+ endCornerRadius,
+ true);
+ } else if (startBlockCenterX - startCornerRadius > endBlockCenterX - endCornerRadius) {
drawRoundedBlock(
- canvas, paint, endPoints.first, endPoints.second, blockWidth, displayedTrackThickness);
+ canvas,
+ paint,
+ endPoints.second,
+ endBlockWidth,
+ displayedTrackThickness,
+ endCornerRadius,
+ endPoints.first,
+ startBlockWidth,
+ displayedTrackThickness,
+ startCornerRadius,
+ false);
} else {
// Draws the path with ROUND cap if the corner radius is half of the track
// thickness.
paint.setStyle(Style.STROKE);
- paint.setStrokeCap(useStrokeCap ? Cap.ROUND : Cap.BUTT);
+ paint.setStrokeCap(spec.useStrokeCap() ? Cap.ROUND : Cap.BUTT);
// If start rounded block is on the left of end rounded block, draws the path with the
// start and end rounded blocks.
@@ -293,14 +339,26 @@ private void drawLine(
phaseFraction);
canvas.drawPath(displayedActivePath, paint);
}
- if (!useStrokeCap && displayedCornerRadius > 0) {
- if (startBlockCenterX > 0) {
+ if (!spec.useStrokeCap()) {
+ if (startBlockCenterX > 0 && startCornerRadius > 0) {
// Draws the start rounded block.
- drawRoundedBlock(canvas, paint, endPoints.first, blockWidth, displayedTrackThickness);
+ drawRoundedBlock(
+ canvas,
+ paint,
+ endPoints.first,
+ startBlockWidth,
+ displayedTrackThickness,
+ startCornerRadius);
}
- if (endBlockCenterX < trackLength) {
+ if (endBlockCenterX < trackLength && endCornerRadius > 0) {
// Draws the end rounded block.
- drawRoundedBlock(canvas, paint, endPoints.second, blockWidth, displayedTrackThickness);
+ drawRoundedBlock(
+ canvas,
+ paint,
+ endPoints.second,
+ endBlockWidth,
+ displayedTrackThickness,
+ endCornerRadius);
}
}
}
@@ -319,52 +377,99 @@ void drawStopIndicator(
// Draws the stop indicator at the end of the track if needed.
paint.setStyle(Style.FILL);
paint.setColor(paintColor);
+ float stopIndicatorCenterX =
+ spec.trackStopIndicatorPadding != null
+ ? spec.trackStopIndicatorPadding.floatValue() + spec.trackStopIndicatorSize / 2f
+ : displayedTrackThickness / 2;
drawRoundedBlock(
canvas,
paint,
new PathPoint(
- new float[] {trackLength / 2 - displayedTrackThickness / 2, 0}, new float[] {1, 0}),
+ new float[] {trackLength / 2 - stopIndicatorCenterX, 0}, new float[] {1, 0}),
spec.trackStopIndicatorSize,
- spec.trackStopIndicatorSize);
+ spec.trackStopIndicatorSize,
+ displayedCornerRadius * spec.trackStopIndicatorSize / displayedTrackThickness);
}
}
+ /** Draws a single rounded block for one of the track ends. */
private void drawRoundedBlock(
@NonNull Canvas canvas,
@NonNull Paint paint,
@NonNull PathPoint drawCenter,
- float markWidth,
- float markHeight) {
- drawRoundedBlock(canvas, paint, drawCenter, null, markWidth, markHeight);
+ float drawWidth,
+ float drawHeight,
+ float drawCornerSize) {
+ drawRoundedBlock(
+ canvas, paint, drawCenter, drawWidth, drawHeight, drawCornerSize, null, 0, 0, 0, false);
}
+ /** Drawas the merged rounded block when two track ends are collapsed. */
private void drawRoundedBlock(
@NonNull Canvas canvas,
@NonNull Paint paint,
@NonNull PathPoint drawCenter,
+ float drawWidth,
+ float drawHeight,
+ float drawCornerSize,
@Nullable PathPoint clipCenter,
- float markWidth,
- float markHeight) {
- markHeight = min(markHeight, displayedTrackThickness);
- float markCornerSize = markHeight * displayedCornerRadius / displayedTrackThickness;
- markCornerSize = min(markWidth / 2, markCornerSize);
- RectF roundedBlock =
- new RectF(-markWidth / 2f, -markHeight / 2f, markWidth / 2f, markHeight / 2f);
+ float clipWidth,
+ float clipHeight,
+ float clipCornerSize,
+ boolean clipRight) {
+ drawHeight = min(drawHeight, displayedTrackThickness);
+ RectF drawRect = new RectF(-drawWidth / 2f, -drawHeight / 2f, drawWidth / 2f, drawHeight / 2f);
paint.setStyle(Style.FILL);
canvas.save();
+ // Clipping!
if (clipCenter != null) {
- // Clipping!
+ clipHeight = min(clipHeight, displayedTrackThickness);
+ clipCornerSize = min(clipWidth / 2, clipCornerSize * clipHeight / displayedTrackThickness);
+ RectF patchRect = new RectF();
+ if (clipRight) {
+ float leftEdgeDiff =
+ (clipCenter.posVec[0] - clipCornerSize) - (drawCenter.posVec[0] - drawCornerSize);
+ if (leftEdgeDiff > 0) {
+ // Clip block is too small. Expand it to include the left edge of the draw block.
+ clipCenter.translate(-leftEdgeDiff / 2, 0);
+ clipWidth += leftEdgeDiff;
+ }
+ // Draw the patch rectangle to fill the gap from the draw block center to its right edge.
+ patchRect.set(0, -drawHeight / 2f, drawWidth / 2f, drawHeight / 2f);
+ } else {
+ float rightEdgeDiff =
+ (clipCenter.posVec[0] + clipCornerSize) - (drawCenter.posVec[0] + drawCornerSize);
+ if (rightEdgeDiff < 0) {
+ // Clip block is too small. Expand it to include the right edge of the draw block.
+ clipCenter.translate(-rightEdgeDiff / 2, 0);
+ clipWidth -= rightEdgeDiff;
+ }
+ // Draw the patch rectangle to fill the gap from the draw block center to its left edge.
+ patchRect.set(-drawWidth / 2f, -drawHeight / 2f, 0, drawHeight / 2f);
+ }
+ RectF clipRect =
+ new RectF(-clipWidth / 2f, -clipHeight / 2f, clipWidth / 2f, clipHeight / 2f);
canvas.translate(clipCenter.posVec[0], clipCenter.posVec[1]);
canvas.rotate(vectorToCanvasRotation(clipCenter.tanVec));
Path clipPath = new Path();
- clipPath.addRoundRect(roundedBlock, markCornerSize, markCornerSize, Direction.CCW);
+ clipPath.addRoundRect(clipRect, clipCornerSize, clipCornerSize, Direction.CCW);
canvas.clipPath(clipPath);
+ // Manually restore to the original canvas transform.
canvas.rotate(-vectorToCanvasRotation(clipCenter.tanVec));
canvas.translate(-clipCenter.posVec[0], -clipCenter.posVec[1]);
+ // Transform to the draw block center and rotation.
+ canvas.translate(drawCenter.posVec[0], drawCenter.posVec[1]);
+ canvas.rotate(vectorToCanvasRotation(drawCenter.tanVec));
+ canvas.drawRect(patchRect, paint);
+ // Draw the draw block.
+ canvas.drawRoundRect(drawRect, drawCornerSize, drawCornerSize, paint);
+ } else {
+ // Transform to the draw block center and rotation.
+ canvas.translate(drawCenter.posVec[0], drawCenter.posVec[1]);
+ canvas.rotate(vectorToCanvasRotation(drawCenter.tanVec));
+ // Draw the draw block.
+ canvas.drawRoundRect(drawRect, drawCornerSize, drawCornerSize, paint);
}
- canvas.translate(drawCenter.posVec[0], drawCenter.posVec[1]);
- canvas.rotate(vectorToCanvasRotation(drawCenter.tanVec));
- canvas.drawRoundRect(roundedBlock, markCornerSize, markCornerSize, paint);
canvas.restore();
}
diff --git a/lib/java/com/google/android/material/progressindicator/LinearProgressIndicator.java b/lib/java/com/google/android/material/progressindicator/LinearProgressIndicator.java
index 173bd152d75..cc1cffdb742 100644
--- a/lib/java/com/google/android/material/progressindicator/LinearProgressIndicator.java
+++ b/lib/java/com/google/android/material/progressindicator/LinearProgressIndicator.java
@@ -33,6 +33,7 @@
import androidx.annotation.RestrictTo.Scope;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
/**
* This class implements the linear type progress indicators.
@@ -168,6 +169,59 @@ public void setTrackCornerRadius(int trackCornerRadius) {
invalidate();
}
+ /**
+ * Returns the radius of the rounded inner corner for the indicator and track in pixels.
+ *
+ * @see #setTrackInnerCornerRadius(int)
+ * @see #setTrackInnerCornerRadiusFraction(int)
+ * @attr ref
+ * com.google.android.material.progressindicator.R.styleable#LinearProgressIndicator_trackInnerCornerRadius
+ */
+ @Px
+ public int getTrackInnerCornerRadius() {
+ return spec.trackInnerCornerRadius;
+ }
+
+ /**
+ * Sets the radius of the rounded inner corner for the indicator and track in pixels.
+ *
+ * @param trackInnerCornerRadius The new corner radius in pixels.
+ * @see #setTrackInnerCornerRadiusFraction(float)
+ * @see #getTrackInnerCornerRadius()
+ * @attr ref
+ * com.google.android.material.progressindicator.R.styleable#LinearProgressIndicator_trackInnerCornerRadius
+ */
+ public void setTrackInnerCornerRadius(@Px int trackInnerCornerRadius) {
+ if (spec.trackInnerCornerRadius != trackInnerCornerRadius) {
+ spec.trackInnerCornerRadius =
+ Math.round(min(trackInnerCornerRadius, spec.trackThickness / 2f));
+ spec.useRelativeTrackInnerCornerRadius = false;
+ spec.hasInnerCornerRadius = true;
+ spec.validateSpec();
+ invalidate();
+ }
+ }
+
+ /**
+ * Sets the radius of the rounded inner corner for the indicator and track in fraction of the
+ * track thickness.
+ *
+ * @param trackInnerCornerRadiusFraction The new corner radius in fraction of the track thickness.
+ * @see #setTrackInnerCornerRadius(int)
+ * @see #getTrackInnerCornerRadius()
+ * @attr ref
+ * com.google.android.material.progressindicator.R.styleable#LinearProgressIndicator_trackInnerCornerRadius
+ */
+ public void setTrackInnerCornerRadiusFraction(float trackInnerCornerRadiusFraction) {
+ if (spec.trackInnerCornerRadiusFraction != trackInnerCornerRadiusFraction) {
+ spec.trackInnerCornerRadiusFraction = min(trackInnerCornerRadiusFraction, 0.5f);
+ spec.useRelativeTrackInnerCornerRadius = true;
+ spec.hasInnerCornerRadius = true;
+ spec.validateSpec();
+ invalidate();
+ }
+ }
+
/**
* Returns the size of the stop indicator at the end of the track in pixels.
*
@@ -196,6 +250,33 @@ public void setTrackStopIndicatorSize(@Px int trackStopIndicatorSize) {
}
}
+ /**
+ * Returns the padding of the stop indicator at the end of the track in pixels.
+ *
+ * @see #setTrackStopIndicatorPadding(int)
+ * @attr ref
+ * com.google.android.material.progressindicator.R.styleable#LinearProgressIndicator_trackStopIndicatorPadding
+ */
+ @Nullable
+ public Integer getTrackStopIndicatorPadding() {
+ return spec.trackStopIndicatorPadding;
+ }
+
+ /**
+ * Sets the padding of the stop indicator at the end of the track in pixels.
+ *
+ * @param trackStopIndicatorPadding The new stop indicator padding in pixels.
+ * @see #getTrackStopIndicatorPadding()
+ * @attr ref
+ * com.google.android.material.progressindicator.R.styleable#LinearProgressIndicator_trackStopIndicatorPadding
+ */
+ public void setTrackStopIndicatorPadding(@Nullable Integer trackStopIndicatorPadding) {
+ if (!Objects.equals(spec.trackStopIndicatorPadding, trackStopIndicatorPadding)) {
+ spec.trackStopIndicatorPadding = trackStopIndicatorPadding;
+ invalidate();
+ }
+ }
+
/**
* Returns the type of indeterminate animation of this progress indicator.
*
diff --git a/lib/java/com/google/android/material/progressindicator/LinearProgressIndicatorSpec.java b/lib/java/com/google/android/material/progressindicator/LinearProgressIndicatorSpec.java
index 512243f1cda..d9e77e83f0c 100644
--- a/lib/java/com/google/android/material/progressindicator/LinearProgressIndicatorSpec.java
+++ b/lib/java/com/google/android/material/progressindicator/LinearProgressIndicatorSpec.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import android.util.TypedValue;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -50,6 +51,14 @@ public final class LinearProgressIndicatorSpec extends BaseProgressIndicatorSpec
/** The size of the stop indicator at the end of the track. */
@Px public int trackStopIndicatorSize;
+ /** The padding of the stop indicator at the end of the track. */
+ @Nullable public Integer trackStopIndicatorPadding;
+
+ @Px public int trackInnerCornerRadius;
+ public float trackInnerCornerRadiusFraction;
+ public boolean useRelativeTrackInnerCornerRadius;
+ public boolean hasInnerCornerRadius;
+
/**
* Instantiates the spec for {@link LinearProgressIndicator}.
*
@@ -96,6 +105,28 @@ public LinearProgressIndicatorSpec(
min(
a.getDimensionPixelSize(R.styleable.LinearProgressIndicator_trackStopIndicatorSize, 0),
trackThickness);
+ if (a.hasValue(R.styleable.LinearProgressIndicator_trackStopIndicatorPadding)) {
+ trackStopIndicatorPadding =
+ a.getDimensionPixelSize(R.styleable.LinearProgressIndicator_trackStopIndicatorPadding, 0);
+ }
+ TypedValue trackInnerCornerRadiusValue =
+ a.peekValue(R.styleable.LinearProgressIndicator_trackInnerCornerRadius);
+ if (trackInnerCornerRadiusValue != null) {
+ if (trackInnerCornerRadiusValue.type == TypedValue.TYPE_DIMENSION) {
+ trackInnerCornerRadius =
+ min(
+ TypedValue.complexToDimensionPixelSize(
+ trackInnerCornerRadiusValue.data, a.getResources().getDisplayMetrics()),
+ trackThickness / 2);
+ useRelativeTrackInnerCornerRadius = false;
+ hasInnerCornerRadius = true;
+ } else if (trackInnerCornerRadiusValue.type == TypedValue.TYPE_FRACTION) {
+ trackInnerCornerRadiusFraction =
+ min(trackInnerCornerRadiusValue.getFraction(1.0f, 1.0f), 0.5f);
+ useRelativeTrackInnerCornerRadius = true;
+ hasInnerCornerRadius = true;
+ }
+ }
a.recycle();
validateSpec();
@@ -104,6 +135,19 @@ public LinearProgressIndicatorSpec(
indicatorDirection == LinearProgressIndicator.INDICATOR_DIRECTION_RIGHT_TO_LEFT;
}
+ public int getTrackInnerCornerRadiusInPx() {
+ return !hasInnerCornerRadius
+ ? getTrackCornerRadiusInPx()
+ : useRelativeTrackInnerCornerRadius
+ ? (int) (trackThickness * trackInnerCornerRadiusFraction)
+ : trackInnerCornerRadius;
+ }
+
+ @Override
+ public boolean useStrokeCap() {
+ return super.useStrokeCap() && getTrackInnerCornerRadiusInPx() == getTrackCornerRadiusInPx();
+ }
+
@Override
void validateSpec() {
super.validateSpec();
@@ -113,7 +157,9 @@ void validateSpec() {
}
if (indeterminateAnimationType
== LinearProgressIndicator.INDETERMINATE_ANIMATION_TYPE_CONTIGUOUS) {
- if (trackCornerRadius > 0 && indicatorTrackGapSize == 0) {
+ if ((getTrackCornerRadiusInPx() > 0
+ || (hasInnerCornerRadius && getTrackInnerCornerRadiusInPx() > 0))
+ && indicatorTrackGapSize == 0) {
// Throws an exception if trying to use the cornered indicator/track with contiguous
// indeterminate animation type without gap.
throw new IllegalArgumentException(
diff --git a/lib/java/com/google/android/material/progressindicator/res-public/values/public.xml b/lib/java/com/google/android/material/progressindicator/res-public/values/public.xml
index 7deb9e55c8f..107eba44766 100644
--- a/lib/java/com/google/android/material/progressindicator/res-public/values/public.xml
+++ b/lib/java/com/google/android/material/progressindicator/res-public/values/public.xml
@@ -36,6 +36,8 @@
+
+
diff --git a/lib/java/com/google/android/material/progressindicator/res/values/attrs.xml b/lib/java/com/google/android/material/progressindicator/res/values/attrs.xml
index 69e1e364a86..94eef038219 100644
--- a/lib/java/com/google/android/material/progressindicator/res/values/attrs.xml
+++ b/lib/java/com/google/android/material/progressindicator/res/values/attrs.xml
@@ -24,9 +24,9 @@
-
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/progressindicator/res/values/styles.xml b/lib/java/com/google/android/material/progressindicator/res/values/styles.xml
index 4130c6c5d03..7bd992941b2 100644
--- a/lib/java/com/google/android/material/progressindicator/res/values/styles.xml
+++ b/lib/java/com/google/android/material/progressindicator/res/values/styles.xml
@@ -15,37 +15,65 @@
~ limitations under the License.
-->
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
@@ -79,40 +107,39 @@
- 0dp
-
-
-
-
-
-
+
+
-
diff --git a/lib/java/com/google/android/material/progressindicator/res/values/tokens.xml b/lib/java/com/google/android/material/progressindicator/res/values/tokens.xml
index fc72cc865ef..0c1c106aa17 100644
--- a/lib/java/com/google/android/material/progressindicator/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/progressindicator/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
@@ -30,12 +30,18 @@
4dp
4dp
4dp
+ 3dp
+ 40dp
+ 20dp
40dp
+ 48dp
4dp
4dp
+ 1.6dp
+ 15dp
diff --git a/lib/java/com/google/android/material/radiobutton/MaterialRadioButton.java b/lib/java/com/google/android/material/radiobutton/MaterialRadioButton.java
index 05d39788eae..be78a1cf227 100644
--- a/lib/java/com/google/android/material/radiobutton/MaterialRadioButton.java
+++ b/lib/java/com/google/android/material/radiobutton/MaterialRadioButton.java
@@ -64,7 +64,7 @@ public MaterialRadioButton(@NonNull Context context) {
}
public MaterialRadioButton(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, R.attr.radioButtonStyle);
+ this(context, attrs, androidx.appcompat.R.attr.radioButtonStyle);
}
public MaterialRadioButton(
@@ -122,7 +122,8 @@ public boolean isUseMaterialThemeColors() {
private ColorStateList getMaterialThemeColorsTintList() {
if (materialThemeColorsTintList == null) {
- int colorControlActivated = MaterialColors.getColor(this, R.attr.colorControlActivated);
+ int colorControlActivated =
+ MaterialColors.getColor(this, androidx.appcompat.R.attr.colorControlActivated);
int colorOnSurface = MaterialColors.getColor(this, R.attr.colorOnSurface);
int colorSurface = MaterialColors.getColor(this, R.attr.colorSurface);
diff --git a/lib/java/com/google/android/material/radiobutton/res/values/tokens.xml b/lib/java/com/google/android/material/radiobutton/res/values/tokens.xml
index dce8ea1610f..8d0bef8d084 100644
--- a/lib/java/com/google/android/material/radiobutton/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/radiobutton/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/resources/MaterialResources.java b/lib/java/com/google/android/material/resources/MaterialResources.java
index 4f8b4a68959..f0804b99dd8 100644
--- a/lib/java/com/google/android/material/resources/MaterialResources.java
+++ b/lib/java/com/google/android/material/resources/MaterialResources.java
@@ -200,9 +200,12 @@ public static int getUnscaledTextSize(
return defValue;
}
- TypedArray a = context.obtainStyledAttributes(textAppearance, R.styleable.TextAppearance);
+ TypedArray a =
+ context.obtainStyledAttributes(
+ textAppearance, androidx.appcompat.R.styleable.TextAppearance);
TypedValue v = new TypedValue();
- boolean available = a.getValue(R.styleable.TextAppearance_android_textSize, v);
+ boolean available =
+ a.getValue(androidx.appcompat.R.styleable.TextAppearance_android_textSize, v);
a.recycle();
if (!available) {
diff --git a/lib/java/com/google/android/material/resources/TextAppearance.java b/lib/java/com/google/android/material/resources/TextAppearance.java
index e19dbf21fa1..b96f5ddba26 100644
--- a/lib/java/com/google/android/material/resources/TextAppearance.java
+++ b/lib/java/com/google/android/material/resources/TextAppearance.java
@@ -86,34 +86,53 @@ public class TextAppearance {
/** Parses the given TextAppearance style resource. */
public TextAppearance(@NonNull Context context, @StyleRes int id) {
- TypedArray a = context.obtainStyledAttributes(id, R.styleable.TextAppearance);
+ TypedArray a =
+ context.obtainStyledAttributes(id, androidx.appcompat.R.styleable.TextAppearance);
- setTextSize(a.getDimension(R.styleable.TextAppearance_android_textSize, 0f));
+ setTextSize(
+ a.getDimension(
+ androidx.appcompat.R.styleable.TextAppearance_android_textSize, 0f));
setTextColor(
MaterialResources.getColorStateList(
- context, a, R.styleable.TextAppearance_android_textColor));
+ context, a, androidx.appcompat.R.styleable.TextAppearance_android_textColor));
textColorHint =
MaterialResources.getColorStateList(
- context, a, R.styleable.TextAppearance_android_textColorHint);
+ context,
+ a,
+ androidx.appcompat.R.styleable.TextAppearance_android_textColorHint);
textColorLink =
MaterialResources.getColorStateList(
- context, a, R.styleable.TextAppearance_android_textColorLink);
- textStyle = a.getInt(R.styleable.TextAppearance_android_textStyle, Typeface.NORMAL);
- typeface = a.getInt(R.styleable.TextAppearance_android_typeface, TYPEFACE_SANS);
+ context,
+ a,
+ androidx.appcompat.R.styleable.TextAppearance_android_textColorLink);
+ textStyle =
+ a.getInt(
+ androidx.appcompat.R.styleable.TextAppearance_android_textStyle,
+ Typeface.NORMAL);
+ typeface =
+ a.getInt(
+ androidx.appcompat.R.styleable.TextAppearance_android_typeface,
+ TYPEFACE_SANS);
int fontFamilyIndex =
MaterialResources.getIndexWithValue(
a,
- R.styleable.TextAppearance_fontFamily,
- R.styleable.TextAppearance_android_fontFamily);
+ androidx.appcompat.R.styleable.TextAppearance_fontFamily,
+ androidx.appcompat.R.styleable.TextAppearance_android_fontFamily);
fontFamilyResourceId = a.getResourceId(fontFamilyIndex, 0);
fontFamily = a.getString(fontFamilyIndex);
- textAllCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
+ textAllCaps =
+ a.getBoolean(androidx.appcompat.R.styleable.TextAppearance_textAllCaps, false);
shadowColor =
MaterialResources.getColorStateList(
- context, a, R.styleable.TextAppearance_android_shadowColor);
- shadowDx = a.getFloat(R.styleable.TextAppearance_android_shadowDx, 0);
- shadowDy = a.getFloat(R.styleable.TextAppearance_android_shadowDy, 0);
- shadowRadius = a.getFloat(R.styleable.TextAppearance_android_shadowRadius, 0);
+ context,
+ a,
+ androidx.appcompat.R.styleable.TextAppearance_android_shadowColor);
+ shadowDx =
+ a.getFloat(androidx.appcompat.R.styleable.TextAppearance_android_shadowDx, 0);
+ shadowDy =
+ a.getFloat(androidx.appcompat.R.styleable.TextAppearance_android_shadowDy, 0);
+ shadowRadius =
+ a.getFloat(androidx.appcompat.R.styleable.TextAppearance_android_shadowRadius, 0);
a.recycle();
diff --git a/lib/java/com/google/android/material/resources/res/values-af/strings.xml b/lib/java/com/google/android/material/resources/res/values-af/strings.xml
index 611c423943a..9f1da9477c8 100644
--- a/lib/java/com/google/android/material/resources/res/values-af/strings.xml
+++ b/lib/java/com/google/android/material/resources/res/values-af/strings.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/lib/java/com/google/android/material/resources/res/values-v28/tokens.xml b/lib/java/com/google/android/material/resources/res/values-v28/tokens.xml
index dbeb8470dbb..3871c4ddd34 100644
--- a/lib/java/com/google/android/material/resources/res/values-v28/tokens.xml
+++ b/lib/java/com/google/android/material/resources/res/values-v28/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/resources/res/values-vi/strings.xml b/lib/java/com/google/android/material/resources/res/values-vi/strings.xml
index 80515822926..333e310abe9 100644
--- a/lib/java/com/google/android/material/resources/res/values-vi/strings.xml
+++ b/lib/java/com/google/android/material/resources/res/values-vi/strings.xml
@@ -1,6 +1,6 @@
+
+
+
+
+
+
+
+ ?attr/colorSurfaceContainer
+ ?attr/colorSurfaceContainer
+ ?attr/colorSecondaryContainer
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSecondaryContainer
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSecondaryContainer
+
+ ?attr/colorOnSurface
+ - 0.38
+ ?attr/colorOnSurface
+ - 0.38
+
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSecondaryContainer
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSecondaryContainer
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSecondaryContainer
+
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSecondaryContainer
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSecondaryContainer
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSecondaryContainer
+
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSecondaryContainer
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSecondaryContainer
+ ?attr/colorOnSurfaceVariant
+ ?attr/colorOnSecondaryContainer
+
+
diff --git a/lib/java/com/google/android/material/resources/res/values/tokens.xml b/lib/java/com/google/android/material/resources/res/values/tokens.xml
index 6e82bf6284c..b5f067f2f3b 100644
--- a/lib/java/com/google/android/material/resources/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/resources/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/resources/res/values/vibrant_toolbar_tokens.xml b/lib/java/com/google/android/material/resources/res/values/vibrant_toolbar_tokens.xml
new file mode 100644
index 00000000000..3756f4069c7
--- /dev/null
+++ b/lib/java/com/google/android/material/resources/res/values/vibrant_toolbar_tokens.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+ ?attr/colorPrimaryContainer
+ ?attr/colorPrimaryContainer
+ ?attr/colorSurfaceContainer
+ ?attr/colorOnPrimaryContainer
+ ?attr/colorOnSurface
+ ?attr/colorOnPrimaryContainer
+ ?attr/colorOnSurface
+
+
+ ?attr/colorOnSurface
+ - 0.38
+ ?attr/colorOnSurface
+ - 0.38
+
+ ?attr/colorOnPrimaryContainer
+ ?attr/colorOnSurface
+ ?attr/colorOnPrimaryContainer
+ ?attr/colorOnSurface
+ ?attr/colorOnPrimaryContainer
+ ?attr/colorOnSurface
+
+ ?attr/colorOnPrimaryContainer
+ ?attr/colorOnSurface
+ ?attr/colorOnPrimaryContainer
+ ?attr/colorOnSurface
+ ?attr/colorOnPrimaryContainer
+ ?attr/colorOnSurface
+
+ ?attr/colorOnPrimaryContainer
+ ?attr/colorOnSurface
+ ?attr/colorOnPrimaryContainer
+ ?attr/colorOnSurface
+ ?attr/colorOnPrimaryContainer
+ ?attr/colorOnSurface
+
+
diff --git a/lib/java/com/google/android/material/ripple/RippleDrawableCompat.java b/lib/java/com/google/android/material/ripple/RippleDrawableCompat.java
index 2c66b02c245..7a1e6bc19cd 100644
--- a/lib/java/com/google/android/material/ripple/RippleDrawableCompat.java
+++ b/lib/java/com/google/android/material/ripple/RippleDrawableCompat.java
@@ -185,8 +185,7 @@ public int getOpacity() {
}
/**
- * A {@link ConstantState} for {@link Ripple}
- *
+ * A {@link ConstantState} for {@link RippleDrawableCompat}
*/
static final class RippleDrawableCompatState extends ConstantState {
diff --git a/lib/java/com/google/android/material/ripple/RippleUtils.java b/lib/java/com/google/android/material/ripple/RippleUtils.java
index c807aaca130..a5be8e128af 100644
--- a/lib/java/com/google/android/material/ripple/RippleUtils.java
+++ b/lib/java/com/google/android/material/ripple/RippleUtils.java
@@ -16,8 +16,6 @@
package com.google.android.material.ripple;
-import com.google.android.material.R;
-
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
@@ -229,7 +227,9 @@ private static Drawable createOvalRipple(@NonNull Context context, @Px int paddi
new InsetDrawable(maskDrawable, padding, padding, padding, padding);
return new RippleDrawable(
MaterialColors.getColorStateList(
- context, R.attr.colorControlHighlight, ColorStateList.valueOf(Color.TRANSPARENT)),
+ context,
+ androidx.appcompat.R.attr.colorControlHighlight,
+ ColorStateList.valueOf(Color.TRANSPARENT)),
null,
maskWithPaddings);
}
diff --git a/lib/java/com/google/android/material/search/SearchBar.java b/lib/java/com/google/android/material/search/SearchBar.java
index 6b9bc0404f4..9faee15ab4c 100644
--- a/lib/java/com/google/android/material/search/SearchBar.java
+++ b/lib/java/com/google/android/material/search/SearchBar.java
@@ -20,6 +20,7 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static com.google.android.material.theme.overlay.MaterialThemeOverlay.wrap;
+import static java.lang.Math.max;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
@@ -35,17 +36,18 @@
import android.os.Parcel;
import android.os.Parcelable;
import androidx.appcompat.content.res.AppCompatResources;
-import androidx.appcompat.view.menu.MenuBuilder;
import androidx.appcompat.widget.ActionMenuView;
import androidx.appcompat.widget.Toolbar;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.LayoutInflater;
-import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.EditText;
+import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.ColorInt;
@@ -55,18 +57,20 @@
import androidx.annotation.MenuRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.Px;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.graphics.drawable.DrawableCompat;
-import androidx.core.view.ViewCompat;
import androidx.core.widget.TextViewCompat;
import androidx.customview.view.AbsSavedState;
import com.google.android.material.appbar.AppBarLayout;
+import com.google.android.material.appbar.AppBarLayout.LiftOnScrollProgressListener;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.internal.ThemeEnforcement;
import com.google.android.material.internal.ToolbarUtils;
+import com.google.android.material.resources.MaterialResources;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.shape.MaterialShapeUtils;
import com.google.android.material.shape.ShapeAppearanceModel;
@@ -133,6 +137,12 @@ public class SearchBar extends Toolbar {
private static final String NAMESPACE_APP = "http://schemas.android.com/apk/res-auto";
private final TextView textView;
+ private final TextView placeholderTextView;
+ private final FrameLayout textViewContainer;
+ private final int backgroundColor;
+
+ private boolean liftOnScroll;
+ @Nullable private final ColorStateList liftOnScrollColor;
private final boolean layoutInflated;
private final boolean defaultMarginsEnabled;
private final SearchBarAnimationHelper searchBarAnimationHelper;
@@ -145,6 +155,23 @@ public class SearchBar extends Toolbar {
private int menuResId = -1;
private boolean defaultScrollFlagsEnabled;
private MaterialShapeDrawable backgroundShape;
+ private boolean textCentered;
+ private int maxWidth;
+ @Nullable private ActionMenuView menuView;
+ @Nullable private ImageButton navIconButton;
+
+ private final LiftOnScrollProgressListener liftColorListener =
+ new LiftOnScrollProgressListener() {
+
+ @Override
+ public void onUpdate(float elevation, int appBarLayoutColor, float progress) {
+ if (liftOnScrollColor != null) {
+ int mixedColor =
+ MaterialColors.layer(backgroundColor, liftOnScrollColor.getDefaultColor(), progress);
+ backgroundShape.setFillColor(ColorStateList.valueOf(mixedColor));
+ }
+ }
+ };
public SearchBar(@NonNull Context context) {
this(context, null);
@@ -170,7 +197,9 @@ public SearchBar(@NonNull Context context, @Nullable AttributeSet attrs, int def
ShapeAppearanceModel shapeAppearanceModel =
ShapeAppearanceModel.builder(context, attrs, defStyleAttr, DEF_STYLE_RES).build();
- int backgroundColor = a.getColor(R.styleable.SearchBar_backgroundTint, 0);
+ backgroundColor = a.getColor(R.styleable.SearchBar_backgroundTint, 0);
+ liftOnScrollColor =
+ MaterialResources.getColorStateList(context, a, R.styleable.SearchBar_liftOnScrollColor);
float elevation = a.getDimension(R.styleable.SearchBar_elevation, 0);
defaultMarginsEnabled = a.getBoolean(R.styleable.SearchBar_defaultMarginsEnabled, true);
defaultScrollFlagsEnabled = a.getBoolean(R.styleable.SearchBar_defaultScrollFlagsEnabled, true);
@@ -186,6 +215,9 @@ public SearchBar(@NonNull Context context, @Nullable AttributeSet attrs, int def
String hint = a.getString(R.styleable.SearchBar_android_hint);
float strokeWidth = a.getDimension(R.styleable.SearchBar_strokeWidth, -1);
int strokeColor = a.getColor(R.styleable.SearchBar_strokeColor, Color.TRANSPARENT);
+ textCentered = a.getBoolean(R.styleable.SearchBar_textCentered, false);
+ liftOnScroll = a.getBoolean(R.styleable.SearchBar_liftOnScroll, false);
+ maxWidth = a.getDimensionPixelSize(R.styleable.SearchBar_android_maxWidth, -1);
a.recycle();
@@ -199,12 +231,18 @@ public SearchBar(@NonNull Context context, @Nullable AttributeSet attrs, int def
layoutInflated = true;
textView = findViewById(R.id.open_search_bar_text_view);
+ placeholderTextView = findViewById(R.id.open_search_bar_placeholder_text_view);
+ textViewContainer = findViewById(R.id.open_search_bar_text_view_container);
- ViewCompat.setElevation(this, elevation);
+ setElevation(elevation);
initTextView(textAppearanceResId, text, hint);
initBackground(shapeAppearanceModel, backgroundColor, elevation, strokeWidth, strokeColor);
}
+ void setPlaceholderText(String string) {
+ placeholderTextView.setText(string);
+ }
+
private void validateAttributes(@Nullable AttributeSet attributeSet) {
if (attributeSet == null) {
return;
@@ -219,6 +257,18 @@ private void validateAttributes(@Nullable AttributeSet attributeSet) {
}
}
+ @Nullable
+ private AppBarLayout getAppBarLayoutParentIfExists() {
+ ViewParent v = getParent();
+ while (v != null) {
+ if (v instanceof AppBarLayout) {
+ return (AppBarLayout) v;
+ }
+ v = v.getParent();
+ }
+ return null;
+ }
+
private void initNavigationIcon() {
// If no navigation icon, set up the default one; otherwise, re-set it for tinting if needed.
setNavigationIcon(getNavigationIcon() == null ? defaultNavigationIcon : getNavigationIcon());
@@ -232,13 +282,11 @@ private void initNavigationIcon() {
private void initTextView(@StyleRes int textAppearanceResId, String text, String hint) {
if (textAppearanceResId != -1) {
TextViewCompat.setTextAppearance(textView, textAppearanceResId);
+ TextViewCompat.setTextAppearance(placeholderTextView, textAppearanceResId);
}
setText(text);
setHint(hint);
- if (getNavigationIcon() == null) {
- ((MarginLayoutParams) textView.getLayoutParams()).setMarginStart(getResources()
- .getDimensionPixelSize(R.dimen.m3_searchbar_text_margin_start_no_navigation_icon));
- }
+ setTextCentered(textCentered);
}
private void initBackground(
@@ -254,7 +302,8 @@ private void initBackground(
backgroundShape.setStroke(strokeWidth, strokeColor);
}
- int rippleColor = MaterialColors.getColor(this, R.attr.colorControlHighlight);
+ int rippleColor =
+ MaterialColors.getColor(this, androidx.appcompat.R.attr.colorControlHighlight);
Drawable background;
backgroundShape.setFillColor(ColorStateList.valueOf(backgroundColor));
background =
@@ -335,7 +384,7 @@ private Drawable maybeTintNavigationIcon(@Nullable Drawable navigationIcon) {
}
Drawable wrappedNavigationIcon = DrawableCompat.wrap(navigationIcon.mutate());
- DrawableCompat.setTint(wrappedNavigationIcon, navigationIconColor);
+ wrappedNavigationIcon.setTint(navigationIconColor);
return wrappedNavigationIcon;
}
@@ -364,20 +413,16 @@ private void setNavigationIconDecorative(boolean decorative) {
@Override
public void inflateMenu(@MenuRes int resId) {
- // Pause dispatching item changes during inflation to improve performance.
- Menu menu = getMenu();
- if (menu instanceof MenuBuilder) {
- ((MenuBuilder) menu).stopDispatchingItemsChanged();
- }
super.inflateMenu(resId);
this.menuResId = resId;
- if (menu instanceof MenuBuilder) {
- ((MenuBuilder) menu).startDispatchingItemsChanged();
- }
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (maxWidth >= 0 && maxWidth < MeasureSpec.getSize(widthMeasureSpec)) {
+ int measureMode = MeasureSpec.getMode(widthMeasureSpec);
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, measureMode);
+ }
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureCenterView(widthMeasureSpec, heightMeasureSpec);
@@ -387,8 +432,55 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- layoutCenterView();
+ if (centerView != null) {
+ layoutViewInCenter(centerView);
+ }
setHandwritingBoundsInsets();
+ if (textView != null) {
+ // If the text is centered, we need to re-layout the textview in the center explicitly instead
+ // of using View gravities because a custom center view in the Toolbar will cause the textview
+ // to be pushed to the side. In this case, we want the textview to be still centered on top of
+ // any center views.
+ if (textCentered) {
+ // Make sure textview does not overlap with any toolbar views (nav icon, menu) or
+ // padding/insets
+ layoutTextViewCenterAvoidToolbarViewsAndPadding();
+ }
+ }
+ }
+
+ /**
+ * Sets whether the {@link SearchBar} lifts when a parent {@link AppBarLayout} lifts on scroll.
+ */
+ public void setLiftOnScroll(boolean liftOnScroll) {
+ this.liftOnScroll = liftOnScroll;
+ if (liftOnScroll) {
+ addLiftOnScrollProgressListener();
+ } else {
+ removeLiftOnScrollProgressListener();
+ }
+ }
+
+ /**
+ * Returns whether or not the {@link SearchBar} lifts when a parent {@link AppBarLayout} lifts
+ * on scroll.
+ */
+ public boolean isLiftOnScroll() {
+ return liftOnScroll;
+ }
+
+ private void addLiftOnScrollProgressListener() {
+ AppBarLayout appBarLayout = getAppBarLayoutParentIfExists();
+ if (appBarLayout != null && liftOnScrollColor != null) {
+ appBarLayout.addLiftOnScrollProgressListener(liftColorListener);
+ }
+ }
+
+ private void removeLiftOnScrollProgressListener() {
+ AppBarLayout appBarLayout = getAppBarLayoutParentIfExists();
+ if (appBarLayout != null) {
+ appBarLayout.removeLiftOnScrollProgressListener(liftColorListener);
+ }
}
@Override
@@ -398,6 +490,15 @@ protected void onAttachedToWindow() {
MaterialShapeUtils.setParentAbsoluteElevation(this, backgroundShape);
setDefaultMargins();
setOrClearDefaultScrollFlags();
+ if (liftOnScroll) {
+ addLiftOnScrollProgressListener();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ removeLiftOnScrollProgressListener();
}
/**
@@ -476,20 +577,98 @@ private void measureCenterView(int widthMeasureSpec, int heightMeasureSpec) {
}
}
- private void layoutCenterView() {
- if (centerView == null) {
+ @Nullable
+ private ActionMenuView findOrGetMenuView() {
+ if (menuView == null) {
+ menuView = ToolbarUtils.getActionMenuView(this);
+ }
+ return menuView;
+ }
+
+ @Nullable
+ private ImageButton findOrGetNavView() {
+ if (navIconButton == null) {
+ navIconButton = ToolbarUtils.getNavigationIconButton(this);
+ }
+ return navIconButton;
+ }
+
+ private void layoutTextViewCenterAvoidToolbarViewsAndPadding() {
+ int textViewContainerLeft = getMeasuredWidth() / 2 - textViewContainer.getMeasuredWidth() / 2;
+ int textViewContainerRight = textViewContainerLeft + textViewContainer.getMeasuredWidth();
+ int textViewContainerTop = getMeasuredHeight() / 2 - textViewContainer.getMeasuredHeight() / 2;
+ int textViewContainerBottom = textViewContainerTop + textViewContainer.getMeasuredHeight();
+ boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ View menuView = findOrGetMenuView();
+ View navIconButton = findOrGetNavView();
+
+ int textViewLeft = textViewContainer.getMeasuredWidth() / 2 - textView.getMeasuredWidth() / 2;
+ int textViewRight = textViewLeft + textView.getMeasuredWidth();
+
+ // left and right refer to the textview's left and right coordinates within the searchbar
+ int left = textViewLeft + textViewContainerLeft;
+ int right = textViewContainerLeft + textViewRight;
+
+ View leftView = isRtl ? menuView : navIconButton;
+ View rightView = isRtl ? navIconButton : menuView;
+ int leftShift = 0;
+ int rightShift = 0;
+ if (leftView != null) {
+ leftShift = max(leftView.getRight() - left, 0);
+ }
+ left += leftShift;
+ right += leftShift;
+ if (rightView != null) {
+ rightShift = max(right - rightView.getLeft(), 0);
+ }
+ left -= rightShift;
+ right -= rightShift;
+ // Make sure to not lay out the view inside the SearchBar padding. paddingLeftAdded and
+ // paddingRightAdded will never be non-zero at the same time, as Toolbar.measure has already
+ // measured the children accounting for padding.
+ int paddingLeftShift = max(getPaddingLeft() - left, getContentInsetLeft() - left);
+ int paddingRightShift =
+ max(
+ right - (getMeasuredWidth() - getPaddingRight()),
+ right - (getMeasuredWidth() - getContentInsetRight()));
+ paddingLeftShift = max(paddingLeftShift, 0);
+ paddingRightShift = max(paddingRightShift, 0);
+
+ int totalShift = leftShift - rightShift + paddingLeftShift - paddingRightShift;
+ // Center the textViewContainer and shift over textViewContainer by the amount that textView
+ // needs to be shifted over; this shifts both the container and the textView, which is necessary so the textView doesn't get
+ // laid outside of the container.
+ textViewContainer.layout(
+ textViewContainerLeft + totalShift,
+ textViewContainerTop,
+ textViewContainerRight + totalShift,
+ textViewContainerBottom);
+ }
+
+ /**
+ * Lays out the given view in the center of the {@link SearchBar}.
+ *
+ * @param view The view to layout in the center.
+ */
+ private void layoutViewInCenter(View view) {
+ if (view == null) {
return;
}
- int centerViewWidth = centerView.getMeasuredWidth();
- int left = getMeasuredWidth() / 2 - centerViewWidth / 2;
- int right = left + centerViewWidth;
+ int viewWidth = view.getMeasuredWidth();
+ int left = getMeasuredWidth() / 2 - viewWidth / 2;
+ int right = left + viewWidth;
- int centerViewHeight = centerView.getMeasuredHeight();
- int top = getMeasuredHeight() / 2 - centerViewHeight / 2;
- int bottom = top + centerViewHeight;
+ int viewHeight = view.getMeasuredHeight();
+ int top = getMeasuredHeight() / 2 - viewHeight / 2;
+ int bottom = top + viewHeight;
- layoutChild(centerView, left, top, right, bottom);
+ layoutChild(
+ view,
+ left,
+ top,
+ right,
+ bottom);
}
private void layoutChild(View child, int left, int top, int right, int bottom) {
@@ -543,6 +722,10 @@ public void setCenterView(@Nullable View view) {
}
}
+ TextView getPlaceholderTextView() {
+ return placeholderTextView;
+ }
+
/** Returns the main {@link TextView} which can be used for hint and search text. */
@NonNull
public TextView getTextView() {
@@ -558,16 +741,42 @@ public CharSequence getText() {
/** Sets the text of main {@link TextView}. */
public void setText(@Nullable CharSequence text) {
textView.setText(text);
+ placeholderTextView.setText(text);
}
/** Sets the text of main {@link TextView}. */
public void setText(@StringRes int textResId) {
textView.setText(textResId);
+ placeholderTextView.setText(textResId);
+ }
+
+ /** Whether or not to center the text within the TextView. */
+ public void setTextCentered(boolean textCentered) {
+ this.textCentered = textCentered;
+ if (textView == null) {
+ return;
+ }
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) textView.getLayoutParams();
+ if (textCentered) {
+ lp.gravity = Gravity.CENTER_HORIZONTAL;
+ textView.setGravity(Gravity.CENTER_HORIZONTAL);
+ } else {
+ lp.gravity = Gravity.NO_GRAVITY;
+ textView.setGravity(Gravity.NO_GRAVITY);
+ }
+ textView.setLayoutParams(lp);
+ placeholderTextView.setLayoutParams(lp);
+ }
+
+ /** Whether or not the text is centered. */
+ public boolean getTextCentered() {
+ return textCentered;
}
/** Clears the text of main {@link TextView}. */
public void clearText() {
textView.setText("");
+ placeholderTextView.setText("");
}
/** Returns the hint of main {@link TextView}. */
@@ -801,7 +1010,21 @@ int getMenuResId() {
}
float getCompatElevation() {
- return backgroundShape != null ? backgroundShape.getElevation() : ViewCompat.getElevation(this);
+ return backgroundShape != null ? backgroundShape.getElevation() : getElevation();
+ }
+
+ /** Sets the max width of SearchBar in pixels. **/
+ public void setMaxWidth(@Px int maxWidth) {
+ if (this.maxWidth != maxWidth) {
+ this.maxWidth = maxWidth;
+ requestLayout();
+ }
+ }
+
+ /** Get the max width of SearchBar in pixels. **/
+ @Px
+ public int getMaxWidth() {
+ return maxWidth;
}
/** Behavior that sets up the scroll-away mode for an {@link SearchBar}. */
diff --git a/lib/java/com/google/android/material/search/SearchBarAnimationHelper.java b/lib/java/com/google/android/material/search/SearchBarAnimationHelper.java
index 65dfdd899fd..7f05385b3fc 100644
--- a/lib/java/com/google/android/material/search/SearchBarAnimationHelper.java
+++ b/lib/java/com/google/android/material/search/SearchBarAnimationHelper.java
@@ -27,7 +27,6 @@
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.view.ViewCompat;
import com.google.android.material.animation.AnimatableView;
import com.google.android.material.animation.AnimationUtils;
import com.google.android.material.appbar.AppBarLayout;
@@ -350,7 +349,7 @@ private AnimatorUpdateListener getExpandedViewBackgroundUpdateListener(
MaterialShapeDrawable expandedViewBackground =
MaterialShapeDrawable.createWithElevationOverlay(expandedView.getContext());
expandedViewBackground.setCornerSize(searchBar.getCornerSize());
- expandedViewBackground.setElevation(ViewCompat.getElevation(searchBar));
+ expandedViewBackground.setElevation(searchBar.getElevation());
return valueAnimator -> {
expandedViewBackground.setInterpolation(1 - valueAnimator.getAnimatedFraction());
diff --git a/lib/java/com/google/android/material/search/SearchView.java b/lib/java/com/google/android/material/search/SearchView.java
index 660005911c1..935f595344d 100644
--- a/lib/java/com/google/android/material/search/SearchView.java
+++ b/lib/java/com/google/android/material/search/SearchView.java
@@ -47,6 +47,7 @@
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
+import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.activity.BackEventCompat;
import androidx.annotation.ColorInt;
@@ -60,6 +61,7 @@
import androidx.annotation.StyleRes;
import androidx.annotation.VisibleForTesting;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import androidx.core.graphics.Insets;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
@@ -143,6 +145,7 @@ public class SearchView extends FrameLayout
final MaterialToolbar toolbar;
final Toolbar dummyToolbar;
final TextView searchPrefix;
+ final LinearLayout textContainer;
final EditText editText;
final ImageButton clearButton;
final View divider;
@@ -214,6 +217,7 @@ public SearchView(@NonNull Context context, @Nullable AttributeSet attrs, int de
toolbar = findViewById(R.id.open_search_view_toolbar);
dummyToolbar = findViewById(R.id.open_search_view_dummy_toolbar);
searchPrefix = findViewById(R.id.open_search_view_search_prefix);
+ textContainer = findViewById(R.id.open_search_view_text_container);
editText = findViewById(R.id.open_search_view_edit_text);
clearButton = findViewById(R.id.open_search_view_clear_button);
divider = findViewById(R.id.open_search_view_divider);
@@ -284,6 +288,9 @@ public void startBackProgress(@NonNull BackEventCompat backEvent) {
if (isHiddenOrHiding() || searchBar == null) {
return;
}
+ if (searchBar != null) {
+ searchBar.setPlaceholderText(editText.getText().toString());
+ }
searchViewAnimationHelper.startBackProgress(backEvent);
}
@@ -471,7 +478,7 @@ private void updateNavigationIconIfNeeded() {
DrawableCompat.wrap(
AppCompatResources.getDrawable(getContext(), navigationIcon).mutate());
if (toolbar.getNavigationIconTint() != null) {
- DrawableCompat.setTint(navigationIconDrawable, toolbar.getNavigationIconTint());
+ navigationIconDrawable.setTint(toolbar.getNavigationIconTint());
}
DrawableCompat.setLayoutDirection(navigationIconDrawable, getLayoutDirection());
toolbar.setNavigationIcon(
@@ -508,9 +515,13 @@ private void setUpToolbarInsetListener() {
boolean isRtl = ViewUtils.isLayoutRtl(toolbar);
int paddingLeft = isRtl ? initialPadding.end : initialPadding.start;
int paddingRight = isRtl ? initialPadding.start : initialPadding.end;
- toolbar.setPadding(
- paddingLeft + insets.getSystemWindowInsetLeft(), initialPadding.top,
- paddingRight + insets.getSystemWindowInsetRight(), initialPadding.bottom);
+ Insets systemBarCutoutInsets =
+ insets.getInsets(
+ WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
+ paddingLeft += systemBarCutoutInsets.left;
+ paddingRight += systemBarCutoutInsets.right;
+
+ toolbar.setPadding(paddingLeft, initialPadding.top, paddingRight, initialPadding.bottom);
return insets;
});
}
@@ -523,7 +534,11 @@ private void setUpStatusBarSpacerInsetListener() {
ViewCompat.setOnApplyWindowInsetsListener(
statusBarSpacer,
(v, insets) -> {
- int systemWindowInsetTop = insets.getSystemWindowInsetTop();
+ int systemWindowInsetTop =
+ insets.getInsets(
+ WindowInsetsCompat.Type.systemBars()
+ | WindowInsetsCompat.Type.displayCutout())
+ .top;
setUpStatusBarSpacer(systemWindowInsetTop);
if (!statusBarSpacerEnabledOverride) {
setStatusBarSpacerEnabledInternal(systemWindowInsetTop > 0);
@@ -539,8 +554,11 @@ private void setUpDividerInsetListener() {
ViewCompat.setOnApplyWindowInsetsListener(
divider,
(v, insets) -> {
- layoutParams.leftMargin = leftMargin + insets.getSystemWindowInsetLeft();
- layoutParams.rightMargin = rightMargin + insets.getSystemWindowInsetRight();
+ Insets systemBarCutoutInsets =
+ insets.getInsets(
+ WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
+ layoutParams.leftMargin = leftMargin + systemBarCutoutInsets.left;
+ layoutParams.rightMargin = rightMargin + systemBarCutoutInsets.right;
return insets;
});
}
@@ -878,7 +896,12 @@ public void hide() {
|| currentTransitionState.equals(TransitionState.HIDING)) {
return;
}
- searchViewAnimationHelper.hide();
+ if (searchBar != null && searchBar.isAttachedToWindow()) {
+ searchBar.setPlaceholderText(editText.getText().toString());
+ searchBar.post(searchViewAnimationHelper::hide);
+ } else {
+ searchViewAnimationHelper.hide();
+ }
}
/** Updates the visibility of the {@link SearchView} without an animation. */
diff --git a/lib/java/com/google/android/material/search/SearchViewAnimationHelper.java b/lib/java/com/google/android/material/search/SearchViewAnimationHelper.java
index 17d1b68b2d2..e76f8a10f49 100644
--- a/lib/java/com/google/android/material/search/SearchViewAnimationHelper.java
+++ b/lib/java/com/google/android/material/search/SearchViewAnimationHelper.java
@@ -30,12 +30,15 @@
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable;
import androidx.appcompat.widget.ActionMenuView;
import androidx.appcompat.widget.Toolbar;
+import android.text.TextUtils;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewParent;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
+import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.activity.BackEventCompat;
import androidx.annotation.NonNull;
@@ -92,6 +95,7 @@ class SearchViewAnimationHelper {
private final FrameLayout toolbarContainer;
private final Toolbar toolbar;
private final Toolbar dummyToolbar;
+ private final LinearLayout textContainer;
private final TextView searchPrefix;
private final EditText editText;
private final ImageButton clearButton;
@@ -116,6 +120,7 @@ class SearchViewAnimationHelper {
this.clearButton = searchView.clearButton;
this.divider = searchView.divider;
this.contentContainer = searchView.contentContainer;
+ this.textContainer = searchView.textContainer;
backHelper = new MaterialMainContainerBackHelper(rootView);
}
@@ -286,7 +291,8 @@ private AnimatorSet getExpandCollapseAnimatorSet(boolean show) {
getDummyToolbarAnimator(show),
getActionMenuViewsAlphaAnimator(show),
getEditTextAnimator(show),
- getSearchPrefixAnimator(show));
+ getSearchPrefixAnimator(show),
+ getTextAnimator(show));
animatorSet.addListener(
new AnimatorListenerAdapter() {
@Override
@@ -297,6 +303,13 @@ public void onAnimationStart(Animator animation) {
@Override
public void onAnimationEnd(Animator animation) {
setContentViewsAlpha(show ? 1 : 0);
+ // Reset edittext and searchbar textview alphas after the animations are finished since
+ // the visibilities for searchview and searchbar have been set accordingly.
+ editText.setAlpha(1);
+ if (searchBar != null) {
+ searchBar.getTextView().setAlpha(1);
+ }
+
// After expanding or collapsing, we should reset the clip bounds so it can react to the
// screen or layout changes. Otherwise it will result in wrong clipping on the layout.
rootView.resetClipBoundsAndCornerRadii();
@@ -429,17 +442,19 @@ private AnimatorSet getButtonsTranslationAnimator(boolean show) {
}
private void addBackButtonTranslationAnimatorIfNeeded(AnimatorSet animatorSet) {
- ImageButton backButton = ToolbarUtils.getNavigationIconButton(toolbar);
- if (backButton == null) {
+ ImageButton searchViewBackButton = ToolbarUtils.getNavigationIconButton(toolbar);
+ if (searchViewBackButton == null) {
return;
}
+ ImageButton searchBarBackButton = ToolbarUtils.getNavigationIconButton(searchBar);
ValueAnimator backButtonAnimatorX =
- ValueAnimator.ofFloat(getFromTranslationXStart(backButton), 0);
- backButtonAnimatorX.addUpdateListener(MultiViewUpdateListener.translationXListener(backButton));
+ ValueAnimator.ofFloat(
+ getTranslationXBetweenViews(searchBarBackButton, searchViewBackButton), 0);
+ backButtonAnimatorX.addUpdateListener(MultiViewUpdateListener.translationXListener(searchViewBackButton));
ValueAnimator backButtonAnimatorY = ValueAnimator.ofFloat(getFromTranslationY(), 0);
- backButtonAnimatorY.addUpdateListener(MultiViewUpdateListener.translationYListener(backButton));
+ backButtonAnimatorY.addUpdateListener(MultiViewUpdateListener.translationYListener(searchViewBackButton));
animatorSet.playTogether(backButtonAnimatorX, backButtonAnimatorY);
}
@@ -454,11 +469,25 @@ private void addBackButtonProgressAnimatorIfNeeded(AnimatorSet animatorSet) {
if (searchView.isAnimatedNavigationIcon()) {
addDrawerArrowDrawableAnimatorIfNeeded(animatorSet, drawable);
addFadeThroughDrawableAnimatorIfNeeded(animatorSet, drawable);
+ addBackButtonAnimatorIfNeeded(animatorSet, backButton);
} else {
setFullDrawableProgressIfNeeded(drawable);
}
}
+ private void addBackButtonAnimatorIfNeeded(AnimatorSet animatorSet, ImageButton backButton) {
+ // If there's no navigation icon on the search bar, we should set the alpha for the button
+ // itself instead of the drawables since the button background has a ripple.
+ if (searchBar == null || searchBar.getNavigationIcon() != null) {
+ return;
+ }
+
+ ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+ animator.addUpdateListener(
+ animation -> backButton.setAlpha((Float) animation.getAnimatedValue()));
+ animatorSet.playTogether(animator);
+ }
+
private void addDrawerArrowDrawableAnimatorIfNeeded(AnimatorSet animatorSet, Drawable drawable) {
if (drawable instanceof DrawerArrowDrawable) {
DrawerArrowDrawable drawerArrowDrawable = (DrawerArrowDrawable) drawable;
@@ -489,29 +518,39 @@ private void setFullDrawableProgressIfNeeded(Drawable drawable) {
}
private void addActionMenuViewAnimatorIfNeeded(AnimatorSet animatorSet) {
- ActionMenuView actionMenuView = ToolbarUtils.getActionMenuView(toolbar);
- if (actionMenuView == null) {
+ ActionMenuView searchViewActionMenuView = ToolbarUtils.getActionMenuView(toolbar);
+ if (searchViewActionMenuView == null) {
return;
}
+ ActionMenuView searchBarActionMenuView = ToolbarUtils.getActionMenuView(searchBar);
ValueAnimator actionMenuViewAnimatorX =
- ValueAnimator.ofFloat(getFromTranslationXEnd(actionMenuView), 0);
+ ValueAnimator.ofFloat(
+ getTranslationXBetweenViews(searchBarActionMenuView, searchViewActionMenuView), 0);
actionMenuViewAnimatorX.addUpdateListener(
- MultiViewUpdateListener.translationXListener(actionMenuView));
+ MultiViewUpdateListener.translationXListener(searchViewActionMenuView));
ValueAnimator actionMenuViewAnimatorY = ValueAnimator.ofFloat(getFromTranslationY(), 0);
actionMenuViewAnimatorY.addUpdateListener(
- MultiViewUpdateListener.translationYListener(actionMenuView));
+ MultiViewUpdateListener.translationYListener(searchViewActionMenuView));
animatorSet.playTogether(actionMenuViewAnimatorX, actionMenuViewAnimatorY);
}
private Animator getDummyToolbarAnimator(boolean show) {
- return getTranslationAnimator(show, false, dummyToolbar);
+ return getTranslationAnimator(
+ show,
+ dummyToolbar,
+ getFromTranslationXEnd(dummyToolbar),
+ getFromTranslationY());
}
private Animator getHeaderContainerAnimator(boolean show) {
- return getTranslationAnimator(show, false, headerContainer);
+ return getTranslationAnimator(
+ show,
+ headerContainer,
+ getFromTranslationXEnd(headerContainer),
+ getFromTranslationY());
}
private Animator getActionMenuViewsAlphaAnimator(boolean show) {
@@ -531,11 +570,68 @@ private Animator getActionMenuViewsAlphaAnimator(boolean show) {
}
private Animator getSearchPrefixAnimator(boolean show) {
- return getTranslationAnimator(show, true, searchPrefix);
+ return getTranslationAnimatorForText(show, searchPrefix);
}
private Animator getEditTextAnimator(boolean show) {
- return getTranslationAnimator(show, true, editText);
+ return getTranslationAnimatorForText(show, editText);
+ }
+
+ private AnimatorSet getTextAnimator(boolean show) {
+ AnimatorSet animatorSet = new AnimatorSet();
+ addTextFadeAnimatorIfNeeded(animatorSet);
+ animatorSet.setDuration(show ? SHOW_DURATION_MS : HIDE_DURATION_MS);
+ animatorSet.setInterpolator(
+ ReversableAnimatedValueInterpolator.of(show, AnimationUtils.LINEAR_INTERPOLATOR));
+ return animatorSet;
+ }
+
+ private void addTextFadeAnimatorIfNeeded(AnimatorSet animatorSet) {
+ if (searchBar == null || TextUtils.equals(editText.getText(), searchBar.getText())) {
+ return;
+ }
+ // If the searchbar text is not equal to the searchview edittext, we want to fade out the
+ // edittext and fade in the searchbar text
+ ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+ animator.addUpdateListener(
+ animation -> {
+ editText.setAlpha((Float) animation.getAnimatedValue());
+ searchBar.getTextView().setAlpha(1 - (Float) animation.getAnimatedValue());
+ });
+ animatorSet.playTogether(animator);
+ }
+
+ private Animator getTranslationAnimatorForText(boolean show, View v) {
+ TextView textView = searchBar.getPlaceholderTextView();
+ // If the placeholder text is empty, we animate to the searchbar textview instead.
+ // Or if we're showing the searchview, we always animate from the searchbar textview, not
+ // from the placeholder text.
+ if (TextUtils.isEmpty(textView.getText()) || show) {
+ textView = searchBar.getTextView();
+ }
+ int startX =
+ getViewLeftFromSearchViewParent(textView) - (v.getLeft() + textContainer.getLeft());
+ return getTranslationAnimator(show, v, startX, getFromTranslationY());
+ }
+
+ private int getViewLeftFromSearchViewParent(@NonNull View v) {
+ int left = v.getLeft();
+ ViewParent viewParent = v.getParent();
+ while (viewParent instanceof View && viewParent != searchView.getParent()) {
+ left += ((View) viewParent).getLeft();
+ viewParent = viewParent.getParent();
+ }
+ return left;
+ }
+
+ private int getViewTopFromSearchViewParent(@NonNull View v) {
+ int top = v.getTop();
+ ViewParent viewParent = v.getParent();
+ while (viewParent instanceof View && viewParent != searchView.getParent()) {
+ top += ((View) viewParent).getTop();
+ viewParent = viewParent.getParent();
+ }
+ return top;
}
private Animator getContentAnimator(boolean show) {
@@ -581,12 +677,11 @@ private Animator getContentScaleAnimator(boolean show) {
return animatorScale;
}
- private Animator getTranslationAnimator(boolean show, boolean anchoredToStart, View view) {
- int startX = anchoredToStart ? getFromTranslationXStart(view) : getFromTranslationXEnd(view);
+ private Animator getTranslationAnimator(boolean show, View view, int startX, int startY) {
ValueAnimator animatorX = ValueAnimator.ofFloat(startX, 0);
animatorX.addUpdateListener(MultiViewUpdateListener.translationXListener(view));
- ValueAnimator animatorY = ValueAnimator.ofFloat(getFromTranslationY(), 0);
+ ValueAnimator animatorY = ValueAnimator.ofFloat(startY, 0);
animatorY.addUpdateListener(MultiViewUpdateListener.translationYListener(view));
AnimatorSet animatorSet = new AnimatorSet();
@@ -597,24 +692,39 @@ private Animator getTranslationAnimator(boolean show, boolean anchoredToStart, V
return animatorSet;
}
- private int getFromTranslationXStart(View view) {
- int marginStart = ((MarginLayoutParams) view.getLayoutParams()).getMarginStart();
- int paddingStart = searchBar.getPaddingStart();
- return ViewUtils.isLayoutRtl(searchBar)
- ? searchBar.getWidth() - searchBar.getRight() + marginStart - paddingStart
- : searchBar.getLeft() - marginStart + paddingStart;
+ private int getTranslationXBetweenViews(
+ @Nullable View searchBarSubView, @NonNull View searchViewSubView) {
+ // If there is no equivalent for the SearchView subview in the SearchBar, we return the
+ // translation between the SearchBar and the start of the SearchView subview
+ if (searchBarSubView == null) {
+ int marginStart = ((MarginLayoutParams) searchViewSubView.getLayoutParams()).getMarginStart();
+ int paddingStart = searchBar.getPaddingStart();
+ int searchBarLeft = getViewLeftFromSearchViewParent(searchBar);
+ return ViewUtils.isLayoutRtl(searchBar)
+ ? searchBarLeft
+ + searchBar.getWidth()
+ + marginStart
+ - paddingStart
+ - searchView.getRight()
+ : (searchBarLeft - marginStart + paddingStart);
+ }
+ return getViewLeftFromSearchViewParent(searchBarSubView)
+ - getViewLeftFromSearchViewParent(searchViewSubView);
}
private int getFromTranslationXEnd(View view) {
int marginEnd = ((MarginLayoutParams) view.getLayoutParams()).getMarginEnd();
+ int viewLeft = getViewLeftFromSearchViewParent(searchBar);
return ViewUtils.isLayoutRtl(searchBar)
- ? searchBar.getLeft() - marginEnd
- : searchBar.getRight() - searchView.getWidth() + marginEnd;
+ ? viewLeft - marginEnd
+ : viewLeft + searchBar.getWidth() + marginEnd - searchView.getWidth();
}
private int getFromTranslationY() {
- int toolbarMiddleY = (toolbarContainer.getTop() + toolbarContainer.getBottom()) / 2;
- int searchBarMiddleY = (searchBar.getTop() + searchBar.getBottom()) / 2;
+ int toolbarMiddleY = toolbarContainer.getTop() + toolbarContainer.getMeasuredHeight() / 2;
+ int searchBarMiddleY =
+ getViewTopFromSearchViewParent(searchBar)
+ + searchBar.getMeasuredHeight() / 2;
return searchBarMiddleY - toolbarMiddleY;
}
diff --git a/lib/java/com/google/android/material/search/res-public/values/public.xml b/lib/java/com/google/android/material/search/res-public/values/public.xml
index 7fe602eb5f5..69bc422c54e 100644
--- a/lib/java/com/google/android/material/search/res-public/values/public.xml
+++ b/lib/java/com/google/android/material/search/res-public/values/public.xml
@@ -20,11 +20,14 @@
+
+
+
@@ -38,4 +41,10 @@
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/search/res/layout/mtrl_search_bar.xml b/lib/java/com/google/android/material/search/res/layout/mtrl_search_bar.xml
index dda3fd94b6d..8843dd65baa 100644
--- a/lib/java/com/google/android/material/search/res/layout/mtrl_search_bar.xml
+++ b/lib/java/com/google/android/material/search/res/layout/mtrl_search_bar.xml
@@ -16,10 +16,24 @@
-->
-
+
+
+
diff --git a/lib/java/com/google/android/material/search/res/layout/mtrl_search_view.xml b/lib/java/com/google/android/material/search/res/layout/mtrl_search_view.xml
index cdd5133062b..b26b71776da 100644
--- a/lib/java/com/google/android/material/search/res/layout/mtrl_search_view.xml
+++ b/lib/java/com/google/android/material/search/res/layout/mtrl_search_view.xml
@@ -74,6 +74,7 @@
app:navigationContentDescription="@string/searchview_navigation_content_description">
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/search/res/values/styles.xml b/lib/java/com/google/android/material/search/res/values/styles.xml
index 72d92800e03..76f33f3623b 100644
--- a/lib/java/com/google/android/material/search/res/values/styles.xml
+++ b/lib/java/com/google/android/material/search/res/values/styles.xml
@@ -14,7 +14,71 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ 0dp
+ 4dp
+ 8dp
+ 12dp
+ 16dp
+ 20dp
+ 28dp
+ 32dp
+ 48dp
diff --git a/lib/java/com/google/android/material/sidesheet/SideSheetBehavior.java b/lib/java/com/google/android/material/sidesheet/SideSheetBehavior.java
index d425690449b..73e4e6b44f5 100644
--- a/lib/java/com/google/android/material/sidesheet/SideSheetBehavior.java
+++ b/lib/java/com/google/android/material/sidesheet/SideSheetBehavior.java
@@ -365,8 +365,7 @@ public boolean onLayoutChild(
if (materialShapeDrawable != null) {
child.setBackground(materialShapeDrawable);
// Use elevation attr if set on side sheet; otherwise, use elevation of child view.
- materialShapeDrawable.setElevation(
- elevation == -1 ? ViewCompat.getElevation(child) : elevation);
+ materialShapeDrawable.setElevation(elevation == -1 ? child.getElevation() : elevation);
} else if (backgroundTint != null) {
ViewCompat.setBackgroundTintList(child, backgroundTint);
}
diff --git a/lib/java/com/google/android/material/sidesheet/res/values-af/strings.xml b/lib/java/com/google/android/material/sidesheet/res/values-af/strings.xml
index e78e48fc4b5..659dee05df6 100644
--- a/lib/java/com/google/android/material/sidesheet/res/values-af/strings.xml
+++ b/lib/java/com/google/android/material/sidesheet/res/values-af/strings.xml
@@ -1,6 +1,6 @@
- end
- - @dimen/m3_side_sheet_standard_elevation
+ - @dimen/m3_side_sheet_standard_elevation
- @style/ShapeAppearance.M3.Comp.Sheet.Side.Docked.Container.Shape
@@ -45,7 +45,7 @@
explicitly on the modal side sheet View. -->
- @dimen/m3_side_sheet_width
- match_parent
- - @dimen/m3_side_sheet_modal_elevation
+ - @dimen/m3_side_sheet_modal_elevation
- @macro/m3_comp_sheet_side_docked_modal_container_color
- @macro/m3_comp_sheet_side_docked_modal_container_shape
diff --git a/lib/java/com/google/android/material/sidesheet/res/values/tokens.xml b/lib/java/com/google/android/material/sidesheet/res/values/tokens.xml
index 11c37485482..97388c1ffe1 100644
--- a/lib/java/com/google/android/material/sidesheet/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/sidesheet/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/slider/BaseSlider.java b/lib/java/com/google/android/material/slider/BaseSlider.java
index 87607b55b66..20b9d2733e5 100644
--- a/lib/java/com/google/android/material/slider/BaseSlider.java
+++ b/lib/java/com/google/android/material/slider/BaseSlider.java
@@ -29,6 +29,9 @@
import static com.google.android.material.slider.LabelFormatter.LABEL_WITHIN_BOUNDS;
import static com.google.android.material.slider.SliderOrientation.HORIZONTAL;
import static com.google.android.material.slider.SliderOrientation.VERTICAL;
+import static com.google.android.material.slider.TickVisibilityMode.TICK_VISIBILITY_AUTO_HIDE;
+import static com.google.android.material.slider.TickVisibilityMode.TICK_VISIBILITY_AUTO_LIMIT;
+import static com.google.android.material.slider.TickVisibilityMode.TICK_VISIBILITY_HIDDEN;
import static com.google.android.material.theme.overlay.MaterialThemeOverlay.wrap;
import static java.lang.Float.compare;
import static java.lang.Math.abs;
@@ -64,6 +67,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import androidx.appcompat.content.res.AppCompatResources;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
@@ -157,8 +161,10 @@
* discrete mode. This is a short hand for setting both the {@code tickColorActive} and {@code
* tickColorInactive} to the same thing. This takes precedence over {@code tickColorActive}
* and {@code tickColorInactive}.
- * {@code tickVisible}: Whether to show the tick marks. Only used when the slider is in
- * discrete mode.
+ * {@code tickVisible} (deprecated, use {@code tickVisibilityMode} instead): Whether to
+ * show the tick marks. Only used when the slider is in discrete mode.
+ * {@code tickVisibilityMode}: Mode to specify the visibility of tick marks. Only used when
+ * the slider is in discrete mode.
* {@code trackColorActive}: The color of the active part of the track.
* {@code trackColorInactive}: The color of the inactive part of the track.
* {@code trackColor}: The color of the whole track. This is a short hand for setting both the
@@ -210,6 +216,7 @@
* @attr ref com.google.android.material.R.styleable#Slider_tickColorActive
* @attr ref com.google.android.material.R.styleable#Slider_tickColorInactive
* @attr ref com.google.android.material.R.styleable#Slider_tickVisible
+ * @attr ref com.google.android.material.R.styleable#Slider_tickVisibilityMode
* @attr ref com.google.android.material.R.styleable#Slider_trackColor
* @attr ref com.google.android.material.R.styleable#Slider_trackColorActive
* @attr ref com.google.android.material.R.styleable#Slider_trackColorInactive
@@ -240,8 +247,6 @@ abstract class BaseSlider<
+ " stepSize(%s)";
private static final String EXCEPTION_ILLEGAL_VALUE_FROM =
"valueFrom(%s) must be smaller than valueTo(%s)";
- private static final String EXCEPTION_ILLEGAL_VALUE_TO =
- "valueTo(%s) must be greater than valueFrom(%s)";
private static final String EXCEPTION_ILLEGAL_STEP_SIZE =
"The stepSize(%s) must be 0, or a factor of the valueFrom(%s)-valueTo(%s) range";
private static final String EXCEPTION_ILLEGAL_MIN_SEPARATION =
@@ -265,6 +270,7 @@ abstract class BaseSlider<
private static final double THRESHOLD = .0001;
private static final float THUMB_WIDTH_PRESSED_RATIO = .5f;
private static final int TRACK_CORNER_SIZE_UNSET = -1;
+ private static final float TOUCH_SLOP_RATIO = .8f;
static final int DEF_STYLE_RES = R.style.Widget_MaterialComponents_Slider;
static final int UNIT_VALUE = 1;
@@ -279,6 +285,15 @@ abstract class BaseSlider<
private static final int LABEL_ANIMATION_EXIT_EASING_ATTR =
R.attr.motionEasingEmphasizedAccelerateInterpolator;
+ private static final float TOP_LABEL_PIVOT_X = 0.5f;
+ private static final float TOP_LABEL_PIVOT_Y = 1.2f;
+
+ private static final float LEFT_LABEL_PIVOT_X = 1.2f;
+ private static final float LEFT_LABEL_PIVOT_Y = 0.5f;
+
+ private static final float RIGHT_LABEL_PIVOT_X = -0.2f;
+ private static final float RIGHT_LABEL_PIVOT_Y = 0.5f;
+
@Dimension(unit = Dimension.DP)
private static final int MIN_TOUCH_TARGET_DP = 48;
@@ -329,15 +344,22 @@ abstract class BaseSlider<
private int trackStopIndicatorSize;
private int trackCornerSize;
private int trackInsideCornerSize;
+ private boolean centered = false;
@Nullable private Drawable trackIconActiveStart;
+ private boolean trackIconActiveStartMutated = false;
@Nullable private Drawable trackIconActiveEnd;
+ private boolean trackIconActiveEndMutated = false;
@Nullable private ColorStateList trackIconActiveColor;
@Nullable private Drawable trackIconInactiveStart;
+ private boolean trackIconInactiveStartMutated = false;
@Nullable private Drawable trackIconInactiveEnd;
+ private boolean trackIconInactiveEndMutated = false;
@Nullable private ColorStateList trackIconInactiveColor;
@Px private int trackIconSize;
+ @Px private int trackIconPadding;
private int labelPadding;
- private float touchDownX;
+ private float touchDownAxis1;
+ private float touchDownAxis2;
private MotionEvent lastEvent;
private LabelFormatter formatter;
private boolean thumbIsPressed = false;
@@ -352,7 +374,7 @@ abstract class BaseSlider<
private int focusedThumbIdx = -1;
private float stepSize = 0.0f;
private float[] ticksCoordinates;
- private boolean tickVisible = true;
+ private int tickVisibilityMode;
private int tickActiveRadius;
private int tickInactiveRadius;
private int trackWidth;
@@ -368,7 +390,8 @@ abstract class BaseSlider<
@NonNull private final Path trackPath = new Path();
@NonNull private final RectF activeTrackRect = new RectF();
- @NonNull private final RectF inactiveTrackRect = new RectF();
+ @NonNull private final RectF inactiveTrackLeftRect = new RectF();
+ @NonNull private final RectF inactiveTrackRightRect = new RectF();
@NonNull private final RectF cornerRect = new RectF();
@NonNull private final Rect labelRect = new Rect();
@NonNull private final RectF iconRectF = new RectF();
@@ -504,6 +527,8 @@ private void loadResources(@NonNull Resources resources) {
minTickSpacing = resources.getDimensionPixelSize(R.dimen.mtrl_slider_tick_min_spacing);
labelPadding = resources.getDimensionPixelSize(R.dimen.mtrl_slider_label_padding);
+
+ trackIconPadding = resources.getDimensionPixelOffset(R.dimen.m3_slider_track_icon_padding);
}
private void processAttributes(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -519,6 +544,7 @@ private void processAttributes(Context context, AttributeSet attrs, int defStyle
valueFrom = a.getFloat(R.styleable.Slider_android_valueFrom, 0.0f);
valueTo = a.getFloat(R.styleable.Slider_android_valueTo, 1.0f);
setValues(valueFrom);
+ setCentered(a.getBoolean(R.styleable.Slider_centered, false));
stepSize = a.getFloat(R.styleable.Slider_android_stepSize, 0.0f);
float defaultMinTouchTargetSize =
@@ -566,7 +592,11 @@ private void processAttributes(Context context, AttributeSet attrs, int defStyle
? haloColor
: AppCompatResources.getColorStateList(context, R.color.material_slider_halo_color));
- tickVisible = a.getBoolean(R.styleable.Slider_tickVisible, true);
+ tickVisibilityMode =
+ a.hasValue(R.styleable.Slider_tickVisibilityMode)
+ ? a.getInt(R.styleable.Slider_tickVisibilityMode, -1)
+ : convertToTickVisibilityMode(a.getBoolean(R.styleable.Slider_tickVisible, true));
+
boolean hasTickColor = a.hasValue(R.styleable.Slider_tickColor);
int tickColorInactiveRes =
hasTickColor ? R.styleable.Slider_tickColor : R.styleable.Slider_tickColorInactive;
@@ -653,20 +683,6 @@ private boolean maybeIncreaseTrackSidePadding() {
return true;
}
- private void validateValueFrom() {
- if (valueFrom >= valueTo) {
- throw new IllegalStateException(
- String.format(EXCEPTION_ILLEGAL_VALUE_FROM, valueFrom, valueTo));
- }
- }
-
- private void validateValueTo() {
- if (valueTo <= valueFrom) {
- throw new IllegalStateException(
- String.format(EXCEPTION_ILLEGAL_VALUE_TO, valueTo, valueFrom));
- }
- }
-
private boolean valueLandsOnTick(float value) {
// Check that the value is a multiple of stepSize given the offset of valueFrom.
double result =
@@ -695,6 +711,11 @@ private void validateStepSize() {
}
private void validateValues() {
+ if (valueFrom >= valueTo) {
+ throw new IllegalStateException(
+ String.format(EXCEPTION_ILLEGAL_VALUE_FROM, valueFrom, valueTo));
+ }
+
for (Float value : values) {
if (value < valueFrom || value > valueTo) {
throw new IllegalStateException(
@@ -748,10 +769,8 @@ private void warnAboutFloatingPointError() {
private void validateConfigurationIfDirty() {
if (dirtyConfig) {
- validateValueFrom();
- validateValueTo();
- validateStepSize();
validateValues();
+ validateStepSize();
validateMinSeparation();
warnAboutFloatingPointError();
dirtyConfig = false;
@@ -1473,7 +1492,7 @@ public int getLabelBehavior() {
public void setLabelBehavior(@LabelBehavior int labelBehavior) {
if (this.labelBehavior != labelBehavior) {
this.labelBehavior = labelBehavior;
- requestLayout();
+ updateWidgetLayout(true);
}
}
@@ -1607,7 +1626,7 @@ private boolean maybeIncreaseWidgetThickness() {
}
private void updateRotationMatrix() {
- float pivot = widgetThickness / 2f;
+ float pivot = calculateTrackCenter();
rotationMatrix.reset();
rotationMatrix.setRotate(90, pivot, pivot);
}
@@ -1768,11 +1787,19 @@ public void setTickInactiveTintList(@NonNull ColorStateList tickColor) {
/**
* Returns whether the tick marks are visible. Only used when the slider is in discrete mode.
*
- * @see #setTickVisible(boolean)
* @attr ref com.google.android.material.R.styleable#Slider_tickVisible
*/
public boolean isTickVisible() {
- return tickVisible;
+ switch (tickVisibilityMode) {
+ case TICK_VISIBILITY_AUTO_LIMIT:
+ return true;
+ case TICK_VISIBILITY_AUTO_HIDE:
+ return getDesiredTickCount() <= getMaxTickCount();
+ case TICK_VISIBILITY_HIDDEN:
+ return false;
+ default:
+ throw new IllegalStateException("Unexpected tickVisibilityMode: " + tickVisibilityMode);
+ }
}
/**
@@ -1780,10 +1807,38 @@ public boolean isTickVisible() {
*
* @param tickVisible The visibility of tick marks.
* @attr ref com.google.android.material.R.styleable#Slider_tickVisible
+ * @deprecated Use {@link #setTickVisibilityMode(int)} instead.
*/
+ @Deprecated
public void setTickVisible(boolean tickVisible) {
- if (this.tickVisible != tickVisible) {
- this.tickVisible = tickVisible;
+ setTickVisibilityMode(convertToTickVisibilityMode(tickVisible));
+ }
+
+ @TickVisibilityMode
+ private int convertToTickVisibilityMode(boolean tickVisible) {
+ return tickVisible ? TICK_VISIBILITY_AUTO_LIMIT : TICK_VISIBILITY_HIDDEN;
+ }
+
+ /**
+ * Returns the current tick visibility mode.
+ *
+ * @see #setTickVisibilityMode(int)
+ * @attr ref com.google.android.material.R.styleable#Slider_tickVisibilityMode
+ */
+ @TickVisibilityMode
+ public int getTickVisibilityMode() {
+ return tickVisibilityMode;
+ }
+
+ /**
+ * Sets the tick visibility mode. Only used when the slider is in discrete mode.
+ *
+ * @see #getTickVisibilityMode()
+ * @attr ref com.google.android.material.R.styleable#Slider_tickVisibilityMode
+ */
+ public void setTickVisibilityMode(@TickVisibilityMode int tickVisibilityMode) {
+ if (this.tickVisibilityMode != tickVisibilityMode) {
+ this.tickVisibilityMode = tickVisibilityMode;
postInvalidate();
}
}
@@ -1991,13 +2046,29 @@ public void setTrackInsideCornerSize(@Px int cornerSize) {
* @see #getTrackIconActiveStart()
*/
public void setTrackIconActiveStart(@Nullable Drawable icon) {
- if (this.trackIconActiveStart == icon) {
+ if (icon == trackIconActiveStart) {
return;
}
- this.trackIconActiveStart = icon;
+
+ trackIconActiveStart = icon;
+ trackIconActiveStartMutated = false;
+ updateTrackIconActiveStart();
invalidate();
}
+ private void updateTrackIconActiveStart() {
+ if (trackIconActiveStart != null) {
+ if (!trackIconActiveStartMutated && trackIconActiveColor != null) {
+ trackIconActiveStart = DrawableCompat.wrap(trackIconActiveStart).mutate();
+ trackIconActiveStartMutated = true;
+ }
+
+ if (trackIconActiveStartMutated) {
+ trackIconActiveStart.setTintList(trackIconActiveColor);
+ }
+ }
+ }
+
/**
* Sets the active track start icon.
*
@@ -2036,13 +2107,29 @@ public Drawable getTrackIconActiveStart() {
* @see #getTrackIconActiveEnd()
*/
public void setTrackIconActiveEnd(@Nullable Drawable icon) {
- if (this.trackIconActiveEnd == icon) {
+ if (icon == trackIconActiveEnd) {
return;
}
- this.trackIconActiveEnd = icon;
+
+ trackIconActiveEnd = icon;
+ trackIconActiveEndMutated = false;
+ updateTrackIconActiveEnd();
invalidate();
}
+ private void updateTrackIconActiveEnd() {
+ if (trackIconActiveEnd != null) {
+ if (!trackIconActiveEndMutated && trackIconActiveColor != null) {
+ trackIconActiveEnd = DrawableCompat.wrap(trackIconActiveEnd).mutate();
+ trackIconActiveEndMutated = true;
+ }
+
+ if (trackIconActiveEndMutated) {
+ trackIconActiveEnd.setTintList(trackIconActiveColor);
+ }
+ }
+ }
+
/**
* Sets the active track end icon.
*
@@ -2106,10 +2193,13 @@ public int getTrackIconSize() {
* @see #getTrackIconActiveColor()
*/
public void setTrackIconActiveColor(@Nullable ColorStateList color) {
- if (this.trackIconActiveColor == color) {
+ if (color == trackIconActiveColor) {
return;
}
- this.trackIconActiveColor = color;
+
+ trackIconActiveColor = color;
+ updateTrackIconActiveStart();
+ updateTrackIconActiveEnd();
invalidate();
}
@@ -2134,13 +2224,29 @@ public ColorStateList getTrackIconActiveColor() {
* @see #getTrackIconInactiveStart()
*/
public void setTrackIconInactiveStart(@Nullable Drawable icon) {
- if (this.trackIconInactiveStart == icon) {
+ if (icon == trackIconInactiveStart) {
return;
}
- this.trackIconInactiveStart = icon;
+
+ trackIconInactiveStart = icon;
+ trackIconInactiveStartMutated = false;
+ updateTrackIconInactiveStart();
invalidate();
}
+ private void updateTrackIconInactiveStart() {
+ if (trackIconInactiveStart != null) {
+ if (!trackIconInactiveStartMutated && trackIconInactiveColor != null) {
+ trackIconInactiveStart = DrawableCompat.wrap(trackIconInactiveStart).mutate();
+ trackIconInactiveStartMutated = true;
+ }
+
+ if (trackIconInactiveStartMutated) {
+ trackIconInactiveStart.setTintList(trackIconInactiveColor);
+ }
+ }
+ }
+
/**
* Sets the inactive track start icon.
*
@@ -2179,13 +2285,29 @@ public Drawable getTrackIconInactiveStart() {
* @see #getTrackIconInactiveEnd()
*/
public void setTrackIconInactiveEnd(@Nullable Drawable icon) {
- if (this.trackIconInactiveEnd == icon) {
+ if (icon == trackIconInactiveEnd) {
return;
}
- this.trackIconInactiveEnd = icon;
+
+ trackIconInactiveEnd = icon;
+ trackIconInactiveEndMutated = false;
+ updateTrackIconInactiveEnd();
invalidate();
}
+ private void updateTrackIconInactiveEnd() {
+ if (trackIconInactiveEnd != null) {
+ if (!trackIconInactiveEndMutated && trackIconInactiveColor != null) {
+ trackIconInactiveEnd = DrawableCompat.wrap(trackIconInactiveEnd).mutate();
+ trackIconInactiveEndMutated = true;
+ }
+
+ if (trackIconInactiveEndMutated) {
+ trackIconInactiveEnd.setTintList(trackIconInactiveColor);
+ }
+ }
+ }
+
/**
* Sets the inactive track end icon.
*
@@ -2223,10 +2345,13 @@ public Drawable getTrackIconInactiveEnd() {
* @see #getTrackIconInactiveColor()
*/
public void setTrackIconInactiveColor(@Nullable ColorStateList color) {
- if (this.trackIconInactiveColor == color) {
+ if (color == trackIconInactiveColor) {
return;
}
- this.trackIconInactiveColor = color;
+
+ trackIconInactiveColor = color;
+ updateTrackIconInactiveStart();
+ updateTrackIconInactiveEnd();
invalidate();
}
@@ -2274,6 +2399,30 @@ public void setOrientation(@Orientation int orientation) {
updateWidgetLayout(true);
}
+ /**
+ * Sets the slider to be in centered configuration, meaning the starting value is positioned in
+ * the middle of the slider.
+ *
+ * @param isCentered boolean to use for the slider's centered configuration.
+ * @attr ref com.google.android.material.R.styleable#Slider_centered
+ * @see #isCentered()
+ */
+ public void setCentered(boolean isCentered) {
+ if (this.centered == isCentered) {
+ return;
+ }
+ this.centered = isCentered;
+
+ // if centered, the default value is at the center
+ if (isCentered) {
+ setValues((valueFrom + valueTo) / 2f);
+ } else {
+ setValues(valueFrom);
+ }
+
+ updateWidgetLayout(true);
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -2320,8 +2469,7 @@ private void detachLabelFromContentView(TooltipDrawable label) {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int labelSize = 0;
if (labelBehavior == LABEL_WITHIN_BOUNDS || shouldAlwaysShowLabel()) {
- labelSize =
- isVertical() ? labels.get(0).getIntrinsicWidth() : labels.get(0).getIntrinsicHeight();
+ labelSize = labels.get(0).getIntrinsicHeight();
}
int spec = MeasureSpec.makeMeasureSpec(widgetThickness + labelSize, MeasureSpec.EXACTLY);
if (isVertical()) {
@@ -2337,24 +2485,49 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
updateHaloHotspot();
}
- private void maybeCalculateTicksCoordinates() {
+ private void updateTicksCoordinates() {
+ validateConfigurationIfDirty();
+
if (stepSize <= 0.0f) {
+ updateTicksCoordinates(/* tickCount= */ 0);
return;
}
- validateConfigurationIfDirty();
+ final int tickCount;
+ switch (tickVisibilityMode) {
+ case TICK_VISIBILITY_AUTO_LIMIT:
+ tickCount = min(getDesiredTickCount(), getMaxTickCount());
+ break;
+ case TICK_VISIBILITY_AUTO_HIDE:
+ int desiredTickCount = getDesiredTickCount();
+ tickCount = desiredTickCount <= getMaxTickCount() ? desiredTickCount : 0;
+ break;
+ case TICK_VISIBILITY_HIDDEN:
+ tickCount = 0;
+ break;
+ default:
+ throw new IllegalStateException("Unexpected tickVisibilityMode: " + tickVisibilityMode);
+ }
+
+ updateTicksCoordinates(tickCount);
+ }
+
+ private void updateTicksCoordinates(int tickCount) {
+ if (tickCount == 0) {
+ ticksCoordinates = null;
+ return;
+ }
- int tickCount = (int) ((valueTo - valueFrom) / stepSize + 1);
- // Limit the tickCount if they will be too dense.
- tickCount = min(tickCount, trackWidth / minTickSpacing + 1);
if (ticksCoordinates == null || ticksCoordinates.length != tickCount * 2) {
ticksCoordinates = new float[tickCount * 2];
}
float interval = trackWidth / (float) (tickCount - 1);
+ float trackCenterY = calculateTrackCenter();
+
for (int i = 0; i < tickCount * 2; i += 2) {
ticksCoordinates[i] = trackSidePadding + i / 2f * interval;
- ticksCoordinates[i + 1] = calculateTrackCenter();
+ ticksCoordinates[i + 1] = trackCenterY;
}
if (isVertical()) {
@@ -2362,12 +2535,20 @@ private void maybeCalculateTicksCoordinates() {
}
}
+ private int getDesiredTickCount() {
+ return (int) ((valueTo - valueFrom) / stepSize + 1);
+ }
+
+ private int getMaxTickCount() {
+ return trackWidth / minTickSpacing + 1;
+ }
+
private void updateTrackWidth(int width) {
// Update the visible track width.
trackWidth = max(width - trackSidePadding * 2, 0);
// Update the visible tick coordinates.
- maybeCalculateTicksCoordinates();
+ updateTicksCoordinates();
}
private void updateHaloHotspot() {
@@ -2381,12 +2562,8 @@ private void updateHaloHotspot() {
if (isVertical()) {
rotationMatrix.mapPoints(haloBounds);
}
- DrawableCompat.setHotspotBounds(
- background,
- (int) haloBounds[0],
- (int) haloBounds[1],
- (int) haloBounds[2],
- (int) haloBounds[3]);
+ background.setHotspotBounds(
+ (int) haloBounds[0], (int) haloBounds[1], (int) haloBounds[2], (int) haloBounds[3]);
}
}
}
@@ -2394,7 +2571,7 @@ private void updateHaloHotspot() {
private int calculateTrackCenter() {
return widgetThickness / 2
+ (labelBehavior == LABEL_WITHIN_BOUNDS || shouldAlwaysShowLabel()
- ? isVertical() ? labels.get(0).getIntrinsicWidth() : labels.get(0).getIntrinsicHeight()
+ ? labels.get(0).getIntrinsicHeight()
: 0);
}
@@ -2404,22 +2581,21 @@ protected void onDraw(@NonNull Canvas canvas) {
validateConfigurationIfDirty();
// Update the visible tick coordinates.
- maybeCalculateTicksCoordinates();
+ updateTicksCoordinates();
}
super.onDraw(canvas);
int yCenter = calculateTrackCenter();
- float first = values.get(0);
- float last = values.get(values.size() - 1);
- if (last < valueTo || (values.size() > 1 && first > valueFrom)) {
- drawInactiveTrack(canvas, trackWidth, yCenter);
- }
- if (last > valueFrom) {
- drawActiveTrack(canvas, trackWidth, yCenter);
+ drawInactiveTracks(canvas, trackWidth, yCenter);
+ drawActiveTracks(canvas, trackWidth, yCenter);
+
+ if (isRtl() || isVertical()) {
+ drawTrackIcons(canvas, activeTrackRect, inactiveTrackLeftRect);
+ } else {
+ drawTrackIcons(canvas, activeTrackRect, inactiveTrackRightRect);
}
- drawTrackIcons(canvas, activeTrackRect, inactiveTrackRect);
maybeDrawTicks(canvas);
maybeDrawStopIndicator(canvas, yCenter);
@@ -2443,32 +2619,55 @@ private float[] getActiveRange() {
float left = normalizeValue(values.size() == 1 ? valueFrom : min);
float right = normalizeValue(max);
- // In RTL we draw things in reverse, so swap the left and right range values
- return isRtl() || isVertical() ? new float[] {right, left} : new float[] {left, right};
+ // When centered, the active range is bound by the center.
+ if (isCentered()) {
+ left = min(.5f, right);
+ right = max(.5f, right);
+ }
+
+ // In RTL we draw things in reverse, so swap the left and right range values.
+ return !isCentered() && (isRtl() || isVertical())
+ ? new float[] {right, left}
+ : new float[] {left, right};
}
- private void drawInactiveTrack(@NonNull Canvas canvas, int width, int yCenter) {
+ private void drawInactiveTracks(@NonNull Canvas canvas, int width, int yCenter) {
float[] activeRange = getActiveRange();
- float right = trackSidePadding + activeRange[1] * width;
- if (right < trackSidePadding + width) {
- inactiveTrackRect.set(
- right + thumbTrackGapSize,
- yCenter - trackThickness / 2f,
- trackSidePadding + width + getTrackCornerSize(),
- yCenter + trackThickness / 2f);
- updateTrack(canvas, inactiveTrackPaint, inactiveTrackRect, FullCornerDirection.RIGHT);
- }
-
- // Also draw inactive track to the left if there is any
- float left = trackSidePadding + activeRange[0] * width;
- if (left > trackSidePadding) {
- inactiveTrackRect.set(
- trackSidePadding - getTrackCornerSize(),
- yCenter - trackThickness / 2f,
- left - thumbTrackGapSize,
- yCenter + trackThickness / 2f);
- updateTrack(canvas, inactiveTrackPaint, inactiveTrackRect, FullCornerDirection.LEFT);
+ float top = yCenter - trackThickness / 2f;
+ float bottom = yCenter + trackThickness / 2f;
+
+ drawInactiveTrackSection(
+ trackSidePadding - getTrackCornerSize(),
+ trackSidePadding + activeRange[0] * width - thumbTrackGapSize,
+ top,
+ bottom,
+ canvas,
+ inactiveTrackLeftRect,
+ FullCornerDirection.LEFT);
+ drawInactiveTrackSection(
+ trackSidePadding + activeRange[1] * width + thumbTrackGapSize,
+ trackSidePadding + width + getTrackCornerSize(),
+ top,
+ bottom,
+ canvas,
+ inactiveTrackRightRect,
+ FullCornerDirection.RIGHT);
+ }
+
+ private void drawInactiveTrackSection(
+ float from,
+ float to,
+ float top,
+ float bottom,
+ @NonNull Canvas canvas,
+ RectF rect,
+ FullCornerDirection direction) {
+ if (to - from > getTrackCornerSize() - thumbTrackGapSize) {
+ rect.set(from, top, to, bottom);
+ } else {
+ rect.setEmpty();
}
+ updateTrack(canvas, inactiveTrackPaint, rect, getTrackCornerSize(), direction);
}
/**
@@ -2483,13 +2682,17 @@ private float normalizeValue(float value) {
return normalized;
}
- private void drawActiveTrack(@NonNull Canvas canvas, int width, int yCenter) {
+ private void drawActiveTracks(@NonNull Canvas canvas, int width, int yCenter) {
float[] activeRange = getActiveRange();
float right = trackSidePadding + activeRange[1] * width;
float left = trackSidePadding + activeRange[0] * width;
+ if (left >= right) {
+ activeTrackRect.setEmpty();
+ return;
+ }
FullCornerDirection direction = FullCornerDirection.NONE;
- if (values.size() == 1) { // Only 1 thumb
+ if (values.size() == 1 && !isCentered()) { // Only 1 thumb
direction = isRtl() || isVertical() ? FullCornerDirection.RIGHT : FullCornerDirection.LEFT;
}
@@ -2506,18 +2709,25 @@ private void drawActiveTrack(@NonNull Canvas canvas, int width, int yCenter) {
}
}
+ int trackCornerSize = getTrackCornerSize();
switch (direction) {
case NONE:
- left += thumbTrackGapSize;
- right -= thumbTrackGapSize;
+ if (!isCentered()) {
+ left += thumbTrackGapSize;
+ right -= thumbTrackGapSize;
+ } else if (activeRange[1] == .5f) { // centered, active track ends at the center
+ left += thumbTrackGapSize;
+ } else if (activeRange[0] == .5f) { // centered, active track starts at the center
+ right -= thumbTrackGapSize;
+ }
break;
case LEFT:
- left -= getTrackCornerSize();
+ left -= trackCornerSize;
right -= thumbTrackGapSize;
break;
case RIGHT:
left += thumbTrackGapSize;
- right += getTrackCornerSize();
+ right += trackCornerSize;
break;
default:
// fall through
@@ -2525,55 +2735,82 @@ private void drawActiveTrack(@NonNull Canvas canvas, int width, int yCenter) {
// Nothing to draw if left is bigger than right.
if (left >= right) {
+ activeTrackRect.setEmpty();
continue;
}
activeTrackRect.set(
left, yCenter - trackThickness / 2f, right, yCenter + trackThickness / 2f);
- updateTrack(canvas, activeTrackPaint, activeTrackRect, direction);
+ updateTrack(canvas, activeTrackPaint, activeTrackRect, trackCornerSize, direction);
}
}
+ private float calculateStartTrackCornerSize(float trackCornerSize) {
+ if (values.isEmpty() || !hasGapBetweenThumbAndTrack()) {
+ return trackCornerSize;
+ }
+ int firstIdx = isRtl() || isVertical() ? values.size() - 1 : 0;
+ float currentX = valueToX(values.get(firstIdx)) - trackSidePadding;
+ if (currentX < trackCornerSize) {
+ return max(currentX, trackInsideCornerSize);
+ }
+ return trackCornerSize;
+ }
+
+ private float calculateEndTrackCornerSize(float trackCornerSize) {
+ if (values.isEmpty() || !hasGapBetweenThumbAndTrack()) {
+ return trackCornerSize;
+ }
+ int lastIdx = isRtl() || isVertical() ? 0 : values.size() - 1;
+ float currentX = valueToX(values.get(lastIdx)) - trackSidePadding;
+ if (currentX > trackWidth - trackCornerSize) {
+ return max(trackWidth - currentX, trackInsideCornerSize);
+ }
+ return trackCornerSize;
+ }
+
private void drawTrackIcons(
@NonNull Canvas canvas,
@NonNull RectF activeTrackBounds,
@NonNull RectF inactiveTrackBounds) {
+ if (!hasTrackIcons()) {
+ return;
+ }
+
if (values.size() > 1) {
Log.w(TAG, "Track icons can only be used when only 1 thumb is present.");
}
// draw track start icons
- calculateBoundsAndDrawTrackIcon(
- canvas, activeTrackBounds, trackIconActiveStart, trackIconActiveColor, true);
- calculateBoundsAndDrawTrackIcon(
- canvas, inactiveTrackBounds, trackIconInactiveStart, trackIconInactiveColor, true);
+ calculateBoundsAndDrawTrackIcon(canvas, activeTrackBounds, trackIconActiveStart, true);
+ calculateBoundsAndDrawTrackIcon(canvas, inactiveTrackBounds, trackIconInactiveStart, true);
// draw track end icons
- calculateBoundsAndDrawTrackIcon(
- canvas, activeTrackBounds, trackIconActiveEnd, trackIconActiveColor, false);
- calculateBoundsAndDrawTrackIcon(
- canvas, inactiveTrackBounds, trackIconInactiveEnd, trackIconInactiveColor, false);
+ calculateBoundsAndDrawTrackIcon(canvas, activeTrackBounds, trackIconActiveEnd, false);
+ calculateBoundsAndDrawTrackIcon(canvas, inactiveTrackBounds, trackIconInactiveEnd, false);
+ }
+
+ private boolean hasTrackIcons() {
+ return trackIconActiveStart != null
+ || trackIconActiveEnd != null
+ || trackIconInactiveStart != null
+ || trackIconInactiveEnd != null;
}
private void calculateBoundsAndDrawTrackIcon(
@NonNull Canvas canvas,
@NonNull RectF trackBounds,
@Nullable Drawable icon,
- @Nullable ColorStateList iconColor,
boolean isStart) {
if (icon != null) {
- calculateTrackIconBounds(trackBounds, iconRectF, trackIconSize, isStart);
+ calculateTrackIconBounds(trackBounds, iconRectF, trackIconSize, trackIconPadding, isStart);
if (!iconRectF.isEmpty()) {
- drawTrackIcon(canvas, iconRectF, icon, iconColor);
+ drawTrackIcon(canvas, iconRectF, icon);
}
}
}
private void drawTrackIcon(
- @NonNull Canvas canvas,
- @NonNull RectF iconBounds,
- @NonNull Drawable icon,
- @Nullable ColorStateList color) {
- DrawableCompat.setTintList(icon, color);
+ @NonNull Canvas canvas, @NonNull RectF iconBounds, @NonNull Drawable icon) {
if (isVertical()) {
rotationMatrix.mapRect(iconBounds);
}
@@ -2583,28 +2820,24 @@ private void drawTrackIcon(
}
private void calculateTrackIconBounds(
- @NonNull RectF trackBounds, @NonNull RectF iconBounds, @Px int iconSize, boolean isStart) {
- float iconPadding = getResources().getDimension(R.dimen.m3_slider_track_icon_padding);
- float iconLeft;
- if (isStart) {
- iconLeft =
- isRtl() || isVertical()
- ? trackBounds.right - iconSize - iconPadding
- : trackBounds.left + iconPadding;
- } else {
- iconLeft =
- isRtl() || isVertical()
+ @NonNull RectF trackBounds,
+ @NonNull RectF iconBounds,
+ @Px int iconSize,
+ @Px int iconPadding,
+ boolean isStart) {
+ if (trackBounds.right - trackBounds.left >= iconSize + 2 * iconPadding) {
+ float iconLeft =
+ (isStart ^ (isRtl() || isVertical()))
? trackBounds.left + iconPadding
- : trackBounds.right - iconSize - iconPadding;
- }
- float iconRight = iconLeft + iconSize;
- int iconTop = calculateTrackCenter() - iconSize / 2;
- if (trackBounds.left > iconLeft - iconPadding || trackBounds.right < iconRight + iconPadding) {
+ : trackBounds.right - iconPadding - iconSize;
+ float iconTop = calculateTrackCenter() - iconSize / 2f;
+ float iconRight = iconLeft + iconSize;
+ float iconBottom = iconTop + iconSize;
+ iconBounds.set(iconLeft, iconTop, iconRight, iconBottom);
+ } else {
// not enough space to draw icon
iconBounds.setEmpty();
- return;
}
- iconBounds.set(iconLeft, iconTop, iconRight, iconTop + iconSize);
}
private boolean hasGapBetweenThumbAndTrack() {
@@ -2620,9 +2853,13 @@ private enum FullCornerDirection {
}
private void updateTrack(
- Canvas canvas, Paint paint, RectF bounds, FullCornerDirection direction) {
- float leftCornerSize = getTrackCornerSize();
- float rightCornerSize = getTrackCornerSize();
+ Canvas canvas, Paint paint, RectF bounds, float cornerSize, FullCornerDirection direction) {
+ if (bounds.isEmpty()) {
+ return;
+ }
+
+ float leftCornerSize = calculateStartTrackCornerSize(cornerSize);
+ float rightCornerSize = calculateEndTrackCornerSize(cornerSize);
switch (direction) {
case BOTH:
break;
@@ -2703,7 +2940,7 @@ private float[] getCornerRadii(float leftSide, float rightSide) {
}
private void maybeDrawTicks(@NonNull Canvas canvas) {
- if (!tickVisible || stepSize <= 0.0f) {
+ if (ticksCoordinates == null || ticksCoordinates.length == 0) {
return;
}
@@ -2719,44 +2956,71 @@ private void maybeDrawTicks(@NonNull Canvas canvas) {
// Draw ticks on the left inactive track (if any).
if (leftActiveTickIndex > 0) {
- canvas.drawPoints(ticksCoordinates, 0, leftActiveTickIndex * 2, inactiveTicksPaint);
+ drawTicks(0, leftActiveTickIndex * 2, canvas, inactiveTicksPaint);
}
// Draw ticks on the active track (if any).
if (leftActiveTickIndex <= rightActiveTickIndex) {
- canvas.drawPoints(
- ticksCoordinates,
- leftActiveTickIndex * 2,
- (rightActiveTickIndex - leftActiveTickIndex + 1) * 2,
- activeTicksPaint);
+ drawTicks(leftActiveTickIndex * 2, (rightActiveTickIndex + 1) * 2, canvas, activeTicksPaint);
}
// Draw ticks on the right inactive track (if any).
if ((rightActiveTickIndex + 1) * 2 < ticksCoordinates.length) {
- canvas.drawPoints(
- ticksCoordinates,
- (rightActiveTickIndex + 1) * 2,
- ticksCoordinates.length - (rightActiveTickIndex + 1) * 2,
- inactiveTicksPaint);
+ drawTicks(
+ (rightActiveTickIndex + 1) * 2, ticksCoordinates.length, canvas, inactiveTicksPaint);
+ }
+ }
+
+ private void drawTicks(int from, int to, Canvas canvas, Paint paint) {
+ for (int i = from; i < to; i += 2) {
+ float coordinateToCheck = isVertical() ? ticksCoordinates[i + 1] : ticksCoordinates[i];
+ if (isOverlappingThumb(coordinateToCheck)
+ || (isCentered() && isOverlappingCenterGap(coordinateToCheck))) {
+ continue;
+ }
+ canvas.drawPoint(ticksCoordinates[i], ticksCoordinates[i + 1], paint);
+ }
+ }
+
+ private boolean isOverlappingThumb(float tickCoordinate) {
+ float threshold = thumbTrackGapSize + thumbWidth / 2f;
+ for (float value : values) {
+ float valueToX = valueToX(value);
+ return tickCoordinate >= valueToX - threshold && tickCoordinate <= valueToX + threshold;
}
+ return false;
+ }
+
+ private boolean isOverlappingCenterGap(float tickCoordinate) {
+ float threshold = thumbTrackGapSize + thumbWidth / 2f;
+ float trackCenter = (trackWidth + trackSidePadding * 2) / 2f;
+ return tickCoordinate >= trackCenter - threshold && tickCoordinate <= trackCenter + threshold;
}
private void maybeDrawStopIndicator(@NonNull Canvas canvas, int yCenter) {
- if (trackStopIndicatorSize <= 0) {
+ if (trackStopIndicatorSize <= 0 || values.isEmpty()) {
return;
}
// Draw stop indicator at the end of the track.
- if (!values.isEmpty() && values.get(values.size() - 1) < valueTo) {
+ if (values.get(values.size() - 1) < valueTo) {
drawStopIndicator(canvas, valueToX(valueTo), yCenter);
}
- // Multiple thumbs, inactive track may be visible at the start.
- if (values.size() > 1 && values.get(0) > valueFrom) {
+ // Centered, multiple thumbs, inactive track may be visible at the start.
+ if (isCentered() || (values.size() > 1 && values.get(0) > valueFrom)) {
drawStopIndicator(canvas, valueToX(valueFrom), yCenter);
}
}
private void drawStopIndicator(@NonNull Canvas canvas, float x, float y) {
+ // Prevent drawing indicator on the thumbs.
+ for (float value : values) {
+ float valueToX = valueToX(value);
+ float threshold = thumbTrackGapSize + thumbWidth / 2f;
+ if (x >= valueToX - threshold && x <= valueToX + threshold) {
+ return;
+ }
+ }
if (isVertical()) {
canvas.drawPoint(y, x, stopIndicatorPaint);
} else {
@@ -2790,7 +3054,7 @@ private void drawThumbDrawable(
@NonNull Canvas canvas, int width, int top, float value, @NonNull Drawable thumbDrawable) {
canvas.save();
if (isVertical()) {
- canvas.setMatrix(rotationMatrix);
+ canvas.concat(rotationMatrix);
}
canvas.translate(
trackSidePadding
@@ -2832,18 +3096,25 @@ public boolean onTouchEvent(@NonNull MotionEvent event) {
return false;
}
- float eventCoordinate = isVertical() ? event.getY() : event.getX();
- touchPosition = (eventCoordinate - trackSidePadding) / trackWidth;
+ float eventCoordinateAxis1 = isVertical() ? event.getY() : event.getX();
+ float eventCoordinateAxis2 = isVertical() ? event.getX() : event.getY();
+ touchPosition = (eventCoordinateAxis1 - trackSidePadding) / trackWidth;
touchPosition = max(0, touchPosition);
touchPosition = min(1, touchPosition);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- touchDownX = eventCoordinate;
+ touchDownAxis1 = eventCoordinateAxis1;
+ touchDownAxis2 = eventCoordinateAxis2;
// If we're inside a vertical scrolling container,
// we should start dragging in ACTION_MOVE
- if (isPotentialVerticalScroll(event)) {
+ if (!isVertical() && isPotentialVerticalScroll(event)) {
+ break;
+ }
+ // If we're inside a horizontal scrolling container,
+ // we should start dragging in ACTION_MOVE
+ if (isVertical() && isPotentialHorizontalScroll(event)) {
break;
}
@@ -2866,8 +3137,15 @@ public boolean onTouchEvent(@NonNull MotionEvent event) {
case MotionEvent.ACTION_MOVE:
if (!thumbIsPressed) {
// Check if we're trying to scroll vertically instead of dragging this Slider
- if (isPotentialVerticalScroll(event)
- && abs(eventCoordinate - touchDownX) < scaledTouchSlop) {
+ if (!isVertical()
+ && isPotentialVerticalScroll(event)
+ && abs(eventCoordinateAxis1 - touchDownAxis1) < scaledTouchSlop) {
+ return false;
+ }
+ // Check if we're trying to scroll horizontally instead of dragging this Slider
+ if (isVertical()
+ && isPotentialHorizontalScroll(event)
+ && abs(eventCoordinateAxis2 - touchDownAxis2) < scaledTouchSlop * TOUCH_SLOP_RATIO) {
return false;
}
getParent().requestDisallowInterceptTouchEvent(true);
@@ -3149,6 +3427,8 @@ private ValueAnimator createLabelAnimator(boolean enter) {
}
private void updateLabels() {
+ updateLabelPivots();
+
switch (labelBehavior) {
case LABEL_GONE:
ensureLabelsRemoved();
@@ -3173,6 +3453,29 @@ private void updateLabels() {
}
}
+ private void updateLabelPivots() {
+ // Set the pivot point so that the label pops up in the direction from the thumb.
+ final float labelPivotX;
+ final float labelPivotY;
+
+ final boolean isVertical = isVertical();
+ final boolean isRtl = isRtl();
+ if (isVertical && isRtl) {
+ labelPivotX = RIGHT_LABEL_PIVOT_X;
+ labelPivotY = RIGHT_LABEL_PIVOT_Y;
+ } else if (isVertical) {
+ labelPivotX = LEFT_LABEL_PIVOT_X;
+ labelPivotY = LEFT_LABEL_PIVOT_Y;
+ } else {
+ labelPivotX = TOP_LABEL_PIVOT_X;
+ labelPivotY = TOP_LABEL_PIVOT_Y;
+ }
+
+ for (TooltipDrawable label : labels) {
+ label.setPivots(labelPivotX, labelPivotY);
+ }
+ }
+
private boolean isSliderVisibleOnScreen() {
final Rect contentViewBounds = new Rect();
ViewUtils.getContentView(this).getHitRect(contentViewBounds);
@@ -3273,17 +3576,24 @@ private void positionLabel(TooltipDrawable label, float value) {
}
private void calculateLabelBounds(TooltipDrawable label, float value) {
- int left =
- trackSidePadding
- + (int) (normalizeValue(value) * trackWidth)
- - label.getIntrinsicWidth() / 2;
- int right = left + label.getIntrinsicWidth();
+ int left;
+ int right;
int bottom;
int top;
if (isVertical() && !isRtl()) {
+ left =
+ trackSidePadding
+ + (int) (normalizeValue(value) * trackWidth)
+ - label.getIntrinsicHeight() / 2;
+ right = left + label.getIntrinsicHeight();
top = calculateTrackCenter() + (labelPadding + thumbHeight / 2);
- bottom = top + label.getIntrinsicHeight();
+ bottom = top + label.getIntrinsicWidth();
} else {
+ left =
+ trackSidePadding
+ + (int) (normalizeValue(value) * trackWidth)
+ - label.getIntrinsicWidth() / 2;
+ right = left + label.getIntrinsicWidth();
bottom = calculateTrackCenter() - (labelPadding + thumbHeight / 2);
top = bottom - label.getIntrinsicHeight();
}
@@ -3315,6 +3625,20 @@ private boolean isInVerticalScrollingContainer() {
return false;
}
+ private boolean isInHorizontalScrollingContainer() {
+ ViewParent p = getParent();
+ while (p instanceof ViewGroup) {
+ ViewGroup parent = (ViewGroup) p;
+ boolean canScrollHorizontally =
+ parent.canScrollHorizontally(1) || parent.canScrollHorizontally(-1);
+ if (canScrollHorizontally && parent.shouldDelayChildPressedState()) {
+ return true;
+ }
+ p = p.getParent();
+ }
+ return false;
+ }
+
private static boolean isMouseEvent(MotionEvent event) {
return event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE;
}
@@ -3323,6 +3647,10 @@ private boolean isPotentialVerticalScroll(MotionEvent event) {
return !isMouseEvent(event) && isInVerticalScrollingContainer();
}
+ private boolean isPotentialHorizontalScroll(MotionEvent event) {
+ return !isMouseEvent(event) && isInHorizontalScrollingContainer();
+ }
+
@SuppressWarnings("unchecked")
private void dispatchOnChangedProgrammatically() {
for (L listener : changeListeners) {
@@ -3483,10 +3811,14 @@ final boolean isRtl() {
return getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
}
- final boolean isVertical() {
+ public boolean isVertical() {
return widgetOrientation == VERTICAL;
}
+ public boolean isCentered() {
+ return centered;
+ }
+
/**
* Attempts to move focus to next or previous thumb independent of layout direction and
* returns whether the focused thumb changed. If focused thumb didn't change, we're at the view
@@ -3752,7 +4084,7 @@ void updateBoundsForVirtualViewId(int virtualViewId, Rect virtualViewBounds) {
virtualViewBounds.set((int) rect.left, (int) rect.top, (int) rect.right, (int) rect.bottom);
}
- private static class AccessibilityHelper extends ExploreByTouchHelper {
+ public static class AccessibilityHelper extends ExploreByTouchHelper {
private final BaseSlider, ?, ?> slider;
final Rect virtualViewBounds = new Rect();
@@ -3775,7 +4107,7 @@ protected int getVirtualViewAt(float x, float y) {
}
@Override
- protected void getVisibleVirtualViews(List virtualViewIds) {
+ protected void getVisibleVirtualViews(@NonNull List virtualViewIds) {
for (int i = 0; i < slider.getValues().size(); i++) {
virtualViewIds.add(i);
}
@@ -3783,7 +4115,7 @@ protected void getVisibleVirtualViews(List virtualViewIds) {
@Override
protected void onPopulateNodeForVirtualView(
- int virtualViewId, AccessibilityNodeInfoCompat info) {
+ int virtualViewId, @NonNull AccessibilityNodeInfoCompat info) {
info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SET_PROGRESS);
@@ -3824,7 +4156,13 @@ protected void onPopulateNodeForVirtualView(
if (values.size() > 1) {
verbalValueType = startOrEndDescription(virtualViewId);
}
- contentDescription.append(String.format(Locale.US, "%s, %s", verbalValueType, verbalValue));
+ CharSequence stateDescription = ViewCompat.getStateDescription(slider);
+ if (!TextUtils.isEmpty(stateDescription)) {
+ info.setStateDescription(stateDescription);
+ } else {
+ contentDescription.append(
+ String.format(Locale.getDefault(), "%s, %s", verbalValueType, verbalValue));
+ }
info.setContentDescription(contentDescription.toString());
slider.updateBoundsForVirtualViewId(virtualViewId, virtualViewBounds);
@@ -3847,7 +4185,7 @@ private String startOrEndDescription(int virtualViewId) {
@Override
protected boolean onPerformActionForVirtualView(
- int virtualViewId, int action, Bundle arguments) {
+ int virtualViewId, int action, @Nullable Bundle arguments) {
if (!slider.isEnabled()) {
return false;
}
@@ -3879,7 +4217,7 @@ protected boolean onPerformActionForVirtualView(
}
// Swap the increment if we're in RTL.
- if (slider.isRtl() || slider.isVertical()) {
+ if (slider.isRtl()) {
increment = -increment;
}
diff --git a/lib/java/com/google/android/material/slider/TickVisibilityMode.java b/lib/java/com/google/android/material/slider/TickVisibilityMode.java
new file mode 100644
index 00000000000..3c6524baa34
--- /dev/null
+++ b/lib/java/com/google/android/material/slider/TickVisibilityMode.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 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.slider;
+
+import androidx.annotation.IntDef;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Mode to specify the visibility of tick marks. */
+@IntDef({
+ TickVisibilityMode.TICK_VISIBILITY_AUTO_LIMIT,
+ TickVisibilityMode.TICK_VISIBILITY_AUTO_HIDE,
+ TickVisibilityMode.TICK_VISIBILITY_HIDDEN
+})
+@Retention(RetentionPolicy.SOURCE)
+public @interface TickVisibilityMode {
+
+ /**
+ * All tick marks will be drawn if they are not spaced too densely. Otherwise, the maximum allowed
+ * number of tick marks will be drawn. Note that in this case the drawn ticks may not match the
+ * actual snap values.
+ */
+ int TICK_VISIBILITY_AUTO_LIMIT = 0;
+
+ /**
+ * All tick marks will be drawn if they are not spaced too densely. Otherwise, the tick marks will
+ * not be drawn.
+ */
+ int TICK_VISIBILITY_AUTO_HIDE = 1;
+
+ /** Tick marks will not be drawn. */
+ int TICK_VISIBILITY_HIDDEN = 2;
+}
diff --git a/lib/java/com/google/android/material/slider/res-public/values/public.xml b/lib/java/com/google/android/material/slider/res-public/values/public.xml
index 9401e49eca3..944cf09b2c6 100644
--- a/lib/java/com/google/android/material/slider/res-public/values/public.xml
+++ b/lib/java/com/google/android/material/slider/res-public/values/public.xml
@@ -17,6 +17,7 @@
+
@@ -35,6 +36,7 @@
+
diff --git a/lib/java/com/google/android/material/slider/res/values-af/strings.xml b/lib/java/com/google/android/material/slider/res/values-af/strings.xml
index efec523c5ef..1ecf2b92f05 100644
--- a/lib/java/com/google/android/material/slider/res/values-af/strings.xml
+++ b/lib/java/com/google/android/material/slider/res/values-af/strings.xml
@@ -1,6 +1,6 @@
+
@@ -70,8 +71,22 @@
-
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/slider/res/values/styles.xml b/lib/java/com/google/android/material/slider/res/values/styles.xml
index 9195016f58d..94ecabbb0c7 100644
--- a/lib/java/com/google/android/material/slider/res/values/styles.xml
+++ b/lib/java/com/google/android/material/slider/res/values/styles.xml
@@ -16,49 +16,43 @@
-->
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/slider/res/values/tokens.xml b/lib/java/com/google/android/material/slider/res/values/tokens.xml
index 390ffb246a6..06513bb1c82 100644
--- a/lib/java/com/google/android/material/slider/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/slider/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
@@ -52,4 +52,45 @@
?attr/colorOnSurfaceInverse
12dp
+
+
+ 16dp
+ 8dp
+
+ 44dp
+
+
+
+ 24dp
+ 8dp
+
+ 44dp
+
+
+
+ 40dp
+ 12dp
+
+ 44dp
+
+ 24dp
+
+
+
+ 56dp
+ 16dp
+
+ 68dp
+
+ 24dp
+
+
+
+ 96dp
+ 28dp
+
+ 108dp
+
+ 32dp
+
diff --git a/lib/java/com/google/android/material/snackbar/BaseTransientBottomBar.java b/lib/java/com/google/android/material/snackbar/BaseTransientBottomBar.java
index 13d2c5b67ff..33f9415f1a8 100644
--- a/lib/java/com/google/android/material/snackbar/BaseTransientBottomBar.java
+++ b/lib/java/com/google/android/material/snackbar/BaseTransientBottomBar.java
@@ -823,10 +823,6 @@ void onLayoutChange() {
}
private void showViewImpl() {
- if (ViewCompat.getAccessibilityPaneTitle(view) == null) {
- ViewCompat.setAccessibilityPaneTitle(
- view, getContext().getString(R.string.snackbar_accessibility_pane_title));
- }
if (shouldAnimate()) {
// If animations are enabled, animate it in
animateViewIn();
@@ -1160,8 +1156,7 @@ protected SnackbarBaseLayout(@NonNull Context context, AttributeSet attrs) {
context = getContext();
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SnackbarLayout);
if (a.hasValue(R.styleable.SnackbarLayout_elevation)) {
- ViewCompat.setElevation(
- this, a.getDimensionPixelSize(R.styleable.SnackbarLayout_elevation, 0));
+ setElevation(a.getDimensionPixelSize(R.styleable.SnackbarLayout_elevation, 0));
}
animationMode = a.getInt(R.styleable.SnackbarLayout_animationMode, ANIMATION_MODE_SLIDE);
if (a.hasValue(R.styleable.SnackbarLayout_shapeAppearance)
@@ -1202,8 +1197,8 @@ public void setBackground(@Nullable Drawable drawable) {
public void setBackgroundDrawable(@Nullable Drawable drawable) {
if (drawable != null && backgroundTint != null) {
drawable = DrawableCompat.wrap(drawable.mutate());
- DrawableCompat.setTintList(drawable, backgroundTint);
- DrawableCompat.setTintMode(drawable, backgroundTintMode);
+ drawable.setTintList(backgroundTint);
+ drawable.setTintMode(backgroundTintMode);
}
super.setBackgroundDrawable(drawable);
}
@@ -1213,8 +1208,8 @@ public void setBackgroundTintList(@Nullable ColorStateList backgroundTint) {
this.backgroundTint = backgroundTint;
if (getBackground() != null) {
Drawable wrappedBackground = DrawableCompat.wrap(getBackground().mutate());
- DrawableCompat.setTintList(wrappedBackground, backgroundTint);
- DrawableCompat.setTintMode(wrappedBackground, backgroundTintMode);
+ wrappedBackground.setTintList(backgroundTint);
+ wrappedBackground.setTintMode(backgroundTintMode);
if (wrappedBackground != getBackground()) {
super.setBackgroundDrawable(wrappedBackground);
}
@@ -1226,7 +1221,7 @@ public void setBackgroundTintMode(@Nullable PorterDuff.Mode backgroundTintMode)
this.backgroundTintMode = backgroundTintMode;
if (getBackground() != null) {
Drawable wrappedBackground = DrawableCompat.wrap(getBackground().mutate());
- DrawableCompat.setTintMode(wrappedBackground, backgroundTintMode);
+ wrappedBackground.setTintMode(backgroundTintMode);
if (wrappedBackground != getBackground()) {
super.setBackgroundDrawable(wrappedBackground);
}
@@ -1263,7 +1258,7 @@ protected void onAttachedToWindow() {
if (baseTransientBottomBar != null) {
baseTransientBottomBar.onAttachedToWindow();
}
- ViewCompat.requestApplyInsets(this);
+ requestApplyInsets();
}
@Override
@@ -1341,7 +1336,7 @@ private Drawable createThemedBackground() {
: createGradientDrawableBackground(backgroundColor, getResources());
if (backgroundTint != null) {
Drawable wrappedDrawable = DrawableCompat.wrap(background);
- DrawableCompat.setTintList(wrappedDrawable, backgroundTint);
+ wrappedDrawable.setTintList(backgroundTint);
return wrappedDrawable;
} else {
return DrawableCompat.wrap(background);
@@ -1381,6 +1376,12 @@ private void setBaseTransientBottomBar(
delegate.setBaseTransientBottomBar(baseTransientBottomBar);
}
+ /**
+ * Called when the user's input indicates that they want to swipe the given view.
+ *
+ * @param child View the user is attempting to swipe
+ * @return true if the view can be dismissed via swiping, false otherwise
+ */
@Override
public boolean canSwipeDismissView(View child) {
return delegate.canSwipeDismissView(child);
diff --git a/lib/java/com/google/android/material/snackbar/Snackbar.java b/lib/java/com/google/android/material/snackbar/Snackbar.java
index 946900306ea..5272af241cc 100644
--- a/lib/java/com/google/android/material/snackbar/Snackbar.java
+++ b/lib/java/com/google/android/material/snackbar/Snackbar.java
@@ -97,6 +97,12 @@ public static class Callback extends BaseCallback {
/** Indicates that the Snackbar was dismissed from a new Snackbar being shown. */
public static final int DISMISS_EVENT_CONSECUTIVE = BaseCallback.DISMISS_EVENT_CONSECUTIVE;
+ /**
+ * Called when the given {@link Snackbar} is visible.
+ *
+ * @param sb The snackbar which is now visible.
+ * @see Snackbar#show()
+ */
@Override
public void onShown(Snackbar sb) {
// Stub implementation to make API check happy.
@@ -164,6 +170,27 @@ public static Snackbar make(
return makeInternal(/* context= */ null, view, text, duration);
}
+ /**
+ * Make a Snackbar to display a message.
+ *
+ * Snackbar will try and find a parent view to hold Snackbar's view from the value given to
+ * {@code view}. Snackbar will walk up the view tree trying to find a suitable parent, which is
+ * defined as a {@link CoordinatorLayout} or the window decor's content view, whichever comes
+ * first.
+ *
+ *
Having a {@link CoordinatorLayout} in your view hierarchy allows Snackbar to enable certain
+ * features, such as swipe-to-dismiss and automatically moving of widgets.
+ *
+ * @param view The view to find a parent from.
+ * @param resId The resource id of the string resource to use. Can be formatted text.
+ * @param duration How long to display the message. Can be {@link #LENGTH_SHORT}, {@link
+ * #LENGTH_LONG}, {@link #LENGTH_INDEFINITE}, or a custom duration in milliseconds.
+ */
+ @NonNull
+ public static Snackbar make(@NonNull View view, @StringRes int resId, @Duration int duration) {
+ return make(view, view.getResources().getText(resId), duration);
+ }
+
/**
* Make a Snackbar to display a message
*
@@ -248,27 +275,6 @@ private static boolean hasSnackbarContentStyleAttrs(@NonNull Context context) {
return snackbarButtonStyleResId != -1 && snackbarTextViewStyleResId != -1;
}
- /**
- * Make a Snackbar to display a message.
- *
- *
Snackbar will try and find a parent view to hold Snackbar's view from the value given to
- * {@code view}. Snackbar will walk up the view tree trying to find a suitable parent, which is
- * defined as a {@link CoordinatorLayout} or the window decor's content view, whichever comes
- * first.
- *
- *
Having a {@link CoordinatorLayout} in your view hierarchy allows Snackbar to enable certain
- * features, such as swipe-to-dismiss and automatically moving of widgets.
- *
- * @param view The view to find a parent from.
- * @param resId The resource id of the string resource to use. Can be formatted text.
- * @param duration How long to display the message. Can be {@link #LENGTH_SHORT}, {@link
- * #LENGTH_LONG}, {@link #LENGTH_INDEFINITE}, or a custom duration in milliseconds.
- */
- @NonNull
- public static Snackbar make(@NonNull View view, @StringRes int resId, @Duration int duration) {
- return make(view, view.getResources().getText(resId), duration);
- }
-
@Nullable
private static ViewGroup findSuitableParent(View view) {
ViewGroup fallback = null;
diff --git a/lib/java/com/google/android/material/snackbar/res/values/styles.xml b/lib/java/com/google/android/material/snackbar/res/values/styles.xml
index 737bc3853d4..8e7030f354f 100644
--- a/lib/java/com/google/android/material/snackbar/res/values/styles.xml
+++ b/lib/java/com/google/android/material/snackbar/res/values/styles.xml
@@ -57,7 +57,7 @@
- @dimen/material_emphasis_high_type
- end
- @integer/design_snackbar_text_max_lines
- - viewStart
+ - viewStart
- ?attr/textAppearanceBody2
- ?attr/colorSurface
- @dimen/design_snackbar_padding_vertical
diff --git a/lib/java/com/google/android/material/snackbar/res/values/tokens.xml b/lib/java/com/google/android/material/snackbar/res/values/tokens.xml
index 9a06809ce27..6d50ca51cd6 100644
--- a/lib/java/com/google/android/material/snackbar/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/snackbar/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/switchmaterial/SwitchMaterial.java b/lib/java/com/google/android/material/switchmaterial/SwitchMaterial.java
index 75a4c8dfdcb..6d573345aa2 100644
--- a/lib/java/com/google/android/material/switchmaterial/SwitchMaterial.java
+++ b/lib/java/com/google/android/material/switchmaterial/SwitchMaterial.java
@@ -63,7 +63,7 @@ public SwitchMaterial(@NonNull Context context) {
}
public SwitchMaterial(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, R.attr.switchStyle);
+ this(context, attrs, androidx.appcompat.R.attr.switchStyle);
}
public SwitchMaterial(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
@@ -120,7 +120,8 @@ public boolean isUseMaterialThemeColors() {
private ColorStateList getMaterialThemeColorsThumbTintList() {
if (materialThemeColorsThumbTintList == null) {
int colorSurface = MaterialColors.getColor(this, R.attr.colorSurface);
- int colorControlActivated = MaterialColors.getColor(this, R.attr.colorControlActivated);
+ int colorControlActivated =
+ MaterialColors.getColor(this, androidx.appcompat.R.attr.colorControlActivated);
float thumbElevation = getResources().getDimension(R.dimen.mtrl_switch_thumb_elevation);
if (elevationOverlayProvider.isThemeElevationOverlayEnabled()) {
thumbElevation += ViewUtils.getParentAbsoluteElevation(this);
@@ -145,7 +146,8 @@ private ColorStateList getMaterialThemeColorsTrackTintList() {
if (materialThemeColorsTrackTintList == null) {
int[] switchTrackColorsList = new int[ENABLED_CHECKED_STATES.length];
int colorSurface = MaterialColors.getColor(this, R.attr.colorSurface);
- int colorControlActivated = MaterialColors.getColor(this, R.attr.colorControlActivated);
+ int colorControlActivated =
+ MaterialColors.getColor(this, androidx.appcompat.R.attr.colorControlActivated);
int colorOnSurface = MaterialColors.getColor(this, R.attr.colorOnSurface);
switchTrackColorsList[0] =
MaterialColors.layer(colorSurface, colorControlActivated, MaterialColors.ALPHA_MEDIUM);
diff --git a/lib/java/com/google/android/material/tabs/TabLayout.java b/lib/java/com/google/android/material/tabs/TabLayout.java
index da5d81b0694..8d381f6bcf9 100644
--- a/lib/java/com/google/android/material/tabs/TabLayout.java
+++ b/lib/java/com/google/android/material/tabs/TabLayout.java
@@ -545,7 +545,7 @@ public TabLayout(@NonNull Context context, @Nullable AttributeSet attrs, int def
MaterialShapeDrawable materialShapeDrawable = new MaterialShapeDrawable();
materialShapeDrawable.setFillColor(backgroundColorStateList);
materialShapeDrawable.initializeElevationOverlay(context);
- materialShapeDrawable.setElevation(ViewCompat.getElevation(this));
+ materialShapeDrawable.setElevation(getElevation());
setBackground(materialShapeDrawable);
}
@@ -2193,7 +2193,7 @@ public Tab setTag(@Nullable Object tag) {
*
* Do not rely on this if using {@link TabLayout#setupWithViewPager(ViewPager)}
*
- * @param id, unique id for this tab
+ * @param id unique id for this tab
*/
@NonNull
@CanIgnoreReturnValue
@@ -2983,9 +2983,9 @@ private void updateTextAndIcon(
? DrawableCompat.wrap(tab.getIcon()).mutate()
: null;
if (icon != null) {
- DrawableCompat.setTintList(icon, tabIconTint);
+ icon.setTintList(tabIconTint);
if (tabIconTintMode != null) {
- DrawableCompat.setTintMode(icon, tabIconTintMode);
+ icon.setTintMode(tabIconTintMode);
}
}
diff --git a/lib/java/com/google/android/material/tabs/res/values/styles.xml b/lib/java/com/google/android/material/tabs/res/values/styles.xml
index ad52e7e2730..32b2d74bf22 100644
--- a/lib/java/com/google/android/material/tabs/res/values/styles.xml
+++ b/lib/java/com/google/android/material/tabs/res/values/styles.xml
@@ -90,6 +90,7 @@
diff --git a/lib/java/com/google/android/material/tabs/res/values/tokens.xml b/lib/java/com/google/android/material/tabs/res/values/tokens.xml
index 30eb338b654..ea5340bbfb7 100644
--- a/lib/java/com/google/android/material/tabs/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/tabs/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/textfield/DropdownMenuEndIconDelegate.java b/lib/java/com/google/android/material/textfield/DropdownMenuEndIconDelegate.java
index 0cdde89b5e7..510796c2556 100644
--- a/lib/java/com/google/android/material/textfield/DropdownMenuEndIconDelegate.java
+++ b/lib/java/com/google/android/material/textfield/DropdownMenuEndIconDelegate.java
@@ -27,6 +27,7 @@
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.os.SystemClock;
import android.text.Editable;
import android.view.MotionEvent;
import android.view.View;
@@ -280,7 +281,7 @@ private void setUpDropdownShowHideBehavior() {
}
private boolean isDropdownPopupActive() {
- long activeFor = System.currentTimeMillis() - dropdownPopupActivatedAt;
+ long activeFor = SystemClock.uptimeMillis() - dropdownPopupActivatedAt;
return activeFor < 0 || activeFor > 300;
}
@@ -297,7 +298,7 @@ private static AutoCompleteTextView castAutoCompleteTextViewOrThrow(EditText edi
private void updateDropdownPopupDirty() {
dropdownPopupDirty = true;
- dropdownPopupActivatedAt = System.currentTimeMillis();
+ dropdownPopupActivatedAt = SystemClock.uptimeMillis();
}
private void setEndIconChecked(boolean checked) {
diff --git a/lib/java/com/google/android/material/textfield/EndCompoundLayout.java b/lib/java/com/google/android/material/textfield/EndCompoundLayout.java
index 7e7fc8d26c2..548a5675b08 100644
--- a/lib/java/com/google/android/material/textfield/EndCompoundLayout.java
+++ b/lib/java/com/google/android/material/textfield/EndCompoundLayout.java
@@ -795,8 +795,7 @@ private void tintEndIconOnError(boolean tintEndIconOnError) {
// Setting the tint here instead of calling setEndIconTintList() in order to preserve and
// restore the icon's original tint.
Drawable endIconDrawable = DrawableCompat.wrap(getEndIconDrawable()).mutate();
- DrawableCompat.setTint(
- endIconDrawable, textInputLayout.getErrorCurrentTextColors());
+ endIconDrawable.setTint(textInputLayout.getErrorCurrentTextColors());
endIconView.setImageDrawable(endIconDrawable);
} else {
applyIconTint(textInputLayout, endIconView, endIconTintList, endIconTintMode);
diff --git a/lib/java/com/google/android/material/textfield/IconHelper.java b/lib/java/com/google/android/material/textfield/IconHelper.java
index f9c898d25b3..2e1e99ec5ae 100644
--- a/lib/java/com/google/android/material/textfield/IconHelper.java
+++ b/lib/java/com/google/android/material/textfield/IconHelper.java
@@ -85,12 +85,12 @@ static void applyIconTint(
int color =
iconTintList.getColorForState(
mergeIconState(textInputLayout, iconView), iconTintList.getDefaultColor());
- DrawableCompat.setTintList(icon, ColorStateList.valueOf(color));
+ icon.setTintList(ColorStateList.valueOf(color));
} else {
- DrawableCompat.setTintList(icon, iconTintList);
+ icon.setTintList(iconTintList);
}
if (iconTintMode != null) {
- DrawableCompat.setTintMode(icon, iconTintMode);
+ icon.setTintMode(iconTintMode);
}
}
@@ -116,7 +116,7 @@ static void refreshIconDrawableState(
mergeIconState(textInputLayout, iconView), colorStateList.getDefaultColor());
icon = DrawableCompat.wrap(icon).mutate();
- DrawableCompat.setTintList(icon, ColorStateList.valueOf(color));
+ icon.setTintList(ColorStateList.valueOf(color));
iconView.setImageDrawable(icon);
}
diff --git a/lib/java/com/google/android/material/textfield/MaterialAutoCompleteTextView.java b/lib/java/com/google/android/material/textfield/MaterialAutoCompleteTextView.java
index 627fbe147cc..7f0e70ad081 100644
--- a/lib/java/com/google/android/material/textfield/MaterialAutoCompleteTextView.java
+++ b/lib/java/com/google/android/material/textfield/MaterialAutoCompleteTextView.java
@@ -51,7 +51,6 @@
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.graphics.drawable.DrawableCompat;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.internal.ManufacturerUtils;
import com.google.android.material.internal.ThemeEnforcement;
@@ -90,7 +89,7 @@ public MaterialAutoCompleteTextView(@NonNull Context context) {
public MaterialAutoCompleteTextView(
@NonNull Context context, @Nullable AttributeSet attributeSet) {
- this(context, attributeSet, R.attr.autoCompleteTextViewStyle);
+ this(context, attributeSet, androidx.appcompat.R.attr.autoCompleteTextViewStyle);
}
public MaterialAutoCompleteTextView(
@@ -105,7 +104,7 @@ public MaterialAutoCompleteTextView(
attributeSet,
R.styleable.MaterialAutoCompleteTextView,
defStyleAttr,
- R.style.Widget_AppCompat_AutoCompleteTextView);
+ androidx.appcompat.R.style.Widget_AppCompat_AutoCompleteTextView);
// Due to a framework bug, setting android:inputType="none" on xml has no effect. Therefore,
// we check it here in case the autoCompleteTextView should be non-editable.
@@ -567,7 +566,7 @@ private Drawable getSelectedItemDrawable() {
// pressed states, but not to other states like focused and hovered. To solve that, we
// create the selectedItemRippleOverlaidColor that will work in those missing states, making
// the selected list item stateful as expected.
- DrawableCompat.setTintList(colorDrawable, selectedItemRippleOverlaidColor);
+ colorDrawable.setTintList(selectedItemRippleOverlaidColor);
return new RippleDrawable(pressedRippleColor, colorDrawable, null);
} else {
return colorDrawable;
diff --git a/lib/java/com/google/android/material/textfield/TextInputEditText.java b/lib/java/com/google/android/material/textfield/TextInputEditText.java
index 4f1b30aed93..88a36aa017a 100644
--- a/lib/java/com/google/android/material/textfield/TextInputEditText.java
+++ b/lib/java/com/google/android/material/textfield/TextInputEditText.java
@@ -66,7 +66,7 @@ public TextInputEditText(@NonNull Context context) {
}
public TextInputEditText(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, R.attr.editTextStyle);
+ this(context, attrs, androidx.appcompat.R.attr.editTextStyle);
}
public TextInputEditText(
diff --git a/lib/java/com/google/android/material/textfield/TextInputLayout.java b/lib/java/com/google/android/material/textfield/TextInputLayout.java
index 08191eb660b..938f1241fec 100644
--- a/lib/java/com/google/android/material/textfield/TextInputLayout.java
+++ b/lib/java/com/google/android/material/textfield/TextInputLayout.java
@@ -868,7 +868,9 @@ private Drawable getEditTextBoxBackground() {
return boxBackground;
}
- int rippleColor = MaterialColors.getColor(editText, R.attr.colorControlHighlight);
+ int rippleColor =
+ MaterialColors.getColor(
+ editText, androidx.appcompat.R.attr.colorControlHighlight);
if (boxBackgroundMode == TextInputLayout.BOX_BACKGROUND_OUTLINE) {
return getOutlinedBoxBackgroundWithRipple(
getContext(), boxBackground, rippleColor, EDIT_TEXT_BACKGROUND_RIPPLE_STATE);
@@ -2877,7 +2879,8 @@ void setTextAppearanceCompatWithErrorFallback(
if (useDefaultColor) {
// Probably caused by our theme not extending from Theme.Design*. Instead
// we manually set something appropriate
- TextViewCompat.setTextAppearance(textView, R.style.TextAppearance_AppCompat_Caption);
+ TextViewCompat.setTextAppearance(
+ textView, androidx.appcompat.R.style.TextAppearance_AppCompat_Caption);
textView.setTextColor(ContextCompat.getColor(getContext(), R.color.design_error));
}
}
@@ -3588,7 +3591,7 @@ public CharSequence getStartIconContentDescription() {
*
* Subsequent calls to {@link #setStartIconDrawable(Drawable)} will automatically mutate the
* drawable and apply the specified tint and tint mode using {@link
- * DrawableCompat#setTintList(Drawable, ColorStateList)}.
+ * Drawable#setTintList(ColorStateList)}.
*
* @param startIconTintList the tint to apply, may be null to clear tint
* @attr ref com.google.android.material.R.styleable#TextInputLayout_startIconTint
@@ -3808,9 +3811,9 @@ public int getEndIconMinSize() {
}
/**
- * Sets {@link ImageView.ScaleType} for the start icon's ImageButton.
+ * Sets {@link ScaleType} for the start icon's ImageButton.
*
- * @param scaleType {@link ImageView.ScaleType} for the start icon's ImageButton.
+ * @param scaleType {@link ScaleType} for the start icon's ImageButton.
* @attr ref android.support.design.button.R.styleable#TextInputLayout_startIconScaleType
* @see #getStartIconScaleType()
*/
@@ -3819,9 +3822,9 @@ public void setStartIconScaleType(@NonNull ScaleType scaleType) {
}
/**
- * Returns the {@link ImageView.ScaleType} for the start icon's ImageButton.
+ * Returns the {@link ScaleType} for the start icon's ImageButton.
*
- * @return Returns the {@link ImageView.ScaleType} for the start icon's ImageButton.
+ * @return Returns the {@link ScaleType} for the start icon's ImageButton.
* @attr ref android.support.design.button.R.styleable#TextInputLayout_startIconScaleType
* @see #setStartIconScaleType(ScaleType)
*/
@@ -3831,9 +3834,9 @@ public ScaleType getStartIconScaleType() {
}
/**
- * Sets {@link ImageView.ScaleType} for the end icon's ImageButton.
+ * Sets {@link ScaleType} for the end icon's ImageButton.
*
- * @param scaleType {@link ImageView.ScaleType} for the end icon's ImageButton.
+ * @param scaleType {@link ScaleType} for the end icon's ImageButton.
* @attr ref android.support.design.button.R.styleable#TextInputLayout_endIconScaleType
* @see #getEndIconScaleType()
*/
@@ -3842,9 +3845,9 @@ public void setEndIconScaleType(@NonNull ScaleType scaleType) {
}
/**
- * Returns the {@link ImageView.ScaleType} for the end icon's ImageButton.
+ * Returns the {@link ScaleType} for the end icon's ImageButton.
*
- * @return Returns the {@link ImageView.ScaleType} for the end icon's ImageButton.
+ * @return Returns the {@link ScaleType} for the end icon's ImageButton.
* @attr ref android.support.design.button.R.styleable#TextInputLayout_endIconScaleType
* @see #setEndIconScaleType(ScaleType)
*/
@@ -3897,7 +3900,7 @@ public CharSequence getEndIconContentDescription() {
*
*
Subsequent calls to {@link #setEndIconDrawable(Drawable)} will automatically mutate the
* drawable and apply the specified tint and tint mode using {@link
- * DrawableCompat#setTintList(Drawable, ColorStateList)}.
+ * Drawable#setTintList(ColorStateList)}.
*
* @param endIconTintList the tint to apply, may be null to clear tint
* @attr ref com.google.android.material.R.styleable#TextInputLayout_endIconTint
@@ -4537,9 +4540,11 @@ private void updateStrokeErrorColor(boolean hasFocus, boolean isHovered) {
@RequiresApi(VERSION_CODES.Q)
private void updateCursorColor() {
- ColorStateList color = cursorColor != null
- ? cursorColor
- : MaterialColors.getColorStateListOrNull(getContext(), R.attr.colorControlActivated);
+ ColorStateList color =
+ cursorColor != null
+ ? cursorColor
+ : MaterialColors.getColorStateListOrNull(
+ getContext(), androidx.appcompat.R.attr.colorControlActivated);
if (editText == null || editText.getTextCursorDrawable() == null) {
// If there's no cursor, return.
@@ -4550,7 +4555,7 @@ private void updateCursorColor() {
if (isOnError() && cursorErrorColor != null) {
color = cursorErrorColor;
}
- DrawableCompat.setTintList(cursorDrawable, color);
+ cursorDrawable.setTintList(color);
}
private void expandHint(boolean animate) {
diff --git a/lib/java/com/google/android/material/textfield/res/values-af/strings.xml b/lib/java/com/google/android/material/textfield/res/values-af/strings.xml
index e116fd1e233..7c5e1ad7c65 100644
--- a/lib/java/com/google/android/material/textfield/res/values-af/strings.xml
+++ b/lib/java/com/google/android/material/textfield/res/values-af/strings.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/lib/java/com/google/android/material/textfield/res/values/tokens_dropdown_menu.xml b/lib/java/com/google/android/material/textfield/res/values/tokens_dropdown_menu.xml
index d922d07f8f9..52f4893f08a 100644
--- a/lib/java/com/google/android/material/textfield/res/values/tokens_dropdown_menu.xml
+++ b/lib/java/com/google/android/material/textfield/res/values/tokens_dropdown_menu.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/textfield/res/values/tokens_textfield.xml b/lib/java/com/google/android/material/textfield/res/values/tokens_textfield.xml
index ba7e564c6ee..f956f4ff318 100644
--- a/lib/java/com/google/android/material/textfield/res/values/tokens_textfield.xml
+++ b/lib/java/com/google/android/material/textfield/res/values/tokens_textfield.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/java/com/google/android/material/theme/overlay/MaterialThemeOverlay.java b/lib/java/com/google/android/material/theme/overlay/MaterialThemeOverlay.java
index beacd68d522..08b189c6c43 100644
--- a/lib/java/com/google/android/material/theme/overlay/MaterialThemeOverlay.java
+++ b/lib/java/com/google/android/material/theme/overlay/MaterialThemeOverlay.java
@@ -45,7 +45,7 @@ public class MaterialThemeOverlay {
private MaterialThemeOverlay() {}
private static final int[] ANDROID_THEME_OVERLAY_ATTRS =
- new int[] {android.R.attr.theme, R.attr.theme};
+ new int[] {android.R.attr.theme, androidx.appcompat.R.attr.theme};
private static final int[] MATERIAL_THEME_OVERLAY_ATTR = new int[] {R.attr.materialThemeOverlay};
@@ -104,7 +104,7 @@ public static Context wrap(
obtainMaterialOverlayIds(context, set, optionalAttrs, defStyleAttr, defStyleRes);
for (int optionalOverlayId : optionalOverlayIds) {
if (optionalOverlayId != 0) {
- contextThemeWrapper = new ContextThemeWrapper(contextThemeWrapper, optionalOverlayId);
+ contextThemeWrapper.getTheme().applyStyle(optionalOverlayId, true);
}
}
diff --git a/lib/java/com/google/android/material/theme/res-public/values/public.xml b/lib/java/com/google/android/material/theme/res-public/values/public.xml
index 31ca7db01f2..e5f42a23d14 100644
--- a/lib/java/com/google/android/material/theme/res-public/values/public.xml
+++ b/lib/java/com/google/android/material/theme/res-public/values/public.xml
@@ -15,6 +15,29 @@
~ limitations under the License.
-->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/theme/res/values-large/themes_base.xml b/lib/java/com/google/android/material/theme/res/values-large/themes_base.xml
index 29128c2e194..bef5060aee2 100644
--- a/lib/java/com/google/android/material/theme/res/values-large/themes_base.xml
+++ b/lib/java/com/google/android/material/theme/res/values-large/themes_base.xml
@@ -16,11 +16,17 @@
-->
+
+
+
+
+ parent="Base.Theme.Material3.Light.Dialog.FixedSize"/>
+ parent="Base.Theme.Material3.Dark.Dialog.FixedSize"/>
diff --git a/lib/java/com/google/android/material/theme/res/values-night/themes_daynight.xml b/lib/java/com/google/android/material/theme/res/values-night/themes_daynight.xml
index ba3ee2f2035..336dc309ace 100644
--- a/lib/java/com/google/android/material/theme/res/values-night/themes_daynight.xml
+++ b/lib/java/com/google/android/material/theme/res/values-night/themes_daynight.xml
@@ -15,6 +15,13 @@
~ limitations under the License.
-->
+
+
+
+
+
+
+
diff --git a/lib/java/com/google/android/material/theme/res/values-night/themes_overlay.xml b/lib/java/com/google/android/material/theme/res/values-night/themes_overlay.xml
index c92bd1431e1..fca5db54168 100644
--- a/lib/java/com/google/android/material/theme/res/values-night/themes_overlay.xml
+++ b/lib/java/com/google/android/material/theme/res/values-night/themes_overlay.xml
@@ -15,6 +15,6 @@
~ limitations under the License.
-->
+
-
diff --git a/lib/java/com/google/android/material/theme/res/values-v31/themes.xml b/lib/java/com/google/android/material/theme/res/values-v31/themes.xml
index 4e1783db51a..2eb553cab42 100644
--- a/lib/java/com/google/android/material/theme/res/values-v31/themes.xml
+++ b/lib/java/com/google/android/material/theme/res/values-v31/themes.xml
@@ -15,6 +15,10 @@
~ limitations under the License.
-->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -25,6 +54,31 @@
-->
diff --git a/lib/java/com/google/android/material/typography/res/values/tokens.xml b/lib/java/com/google/android/material/typography/res/values/tokens.xml
index e749e6823fa..64e51c351a8 100644
--- a/lib/java/com/google/android/material/typography/res/values/tokens.xml
+++ b/lib/java/com/google/android/material/typography/res/values/tokens.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/lib/javatests/com/google/android/material/bottomsheet/BottomSheetDragHandleTest.java b/lib/javatests/com/google/android/material/bottomsheet/BottomSheetDragHandleTest.java
index 0df20bf7f40..7f56dd6fb5f 100644
--- a/lib/javatests/com/google/android/material/bottomsheet/BottomSheetDragHandleTest.java
+++ b/lib/javatests/com/google/android/material/bottomsheet/BottomSheetDragHandleTest.java
@@ -62,31 +62,58 @@ public void setUp() throws Exception {
dragHandleView = new BottomSheetDragHandleView(activity);
}
+ @Test
+ public void test_importantForAccessibilityEnabledByDefault() {
+ activity.addViewToBottomSheet(dragHandleView);
+ assertImportantForAccessibility(true);
+ }
+
+ @Test
+ public void test_importantForAccessibilityNotAltered() {
+ dragHandleView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ activity.addViewToBottomSheet(dragHandleView);
+ shadowOf(accessibilityManager).setEnabled(true);
+ assertThat(dragHandleView.getImportantForAccessibility())
+ .isEqualTo(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+
@Test
public void test_notInteractableWhenDetachedAndAccessibilityDisabled() {
- assertImportantForAccessibility(false);
assertThat(dragHandleView.isClickable()).isFalse();
}
@Test
public void test_notInteractableWhenDetachedAndAccessibilityEnabled() {
shadowOf(accessibilityManager).setEnabled(true);
- assertImportantForAccessibility(false);
assertThat(dragHandleView.isClickable()).isFalse();
}
@Test
- public void test_notInteractableWhenAttachedAndAccessibilityDisabled() {
+ public void test_interactableWhenAttachedAndAccessibilityDisabled() {
activity.addViewToBottomSheet(dragHandleView);
- assertImportantForAccessibility(true);
- assertThat(dragHandleView.isClickable()).isFalse();
+ assertThat(dragHandleView.isClickable()).isTrue();
+ }
+
+ @Test
+ public void test_customClickListenerOverridesInternalClickBehavior() {
+ activity.addViewToBottomSheet(dragHandleView);
+ activity.bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
+ shadowOf(accessibilityManager).setEnabled(true);
+ dragHandleView.setOnClickListener(v -> {
+ // do nothing
+ });
+
+ dragHandleView.callOnClick();
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ assertThat(activity.bottomSheetBehavior.getState())
+ .isEqualTo(BottomSheetBehavior.STATE_EXPANDED);
}
@Test
public void test_notInteractableWhenNotAttachedToBottomSheetAndAccessibilityEnabled() {
activity.addViewToContainer(dragHandleView);
shadowOf(accessibilityManager).setEnabled(true);
- assertImportantForAccessibility(false);
assertThat(dragHandleView.isClickable()).isFalse();
}
@@ -94,7 +121,6 @@ public void test_notInteractableWhenNotAttachedToBottomSheetAndAccessibilityEnab
public void test_interactableWhenAttachedAndAccessibilityEnabled() {
activity.addViewToBottomSheet(dragHandleView);
shadowOf(accessibilityManager).setEnabled(true);
- assertImportantForAccessibility(true);
assertThat(dragHandleView.isClickable()).isTrue();
}
@@ -208,7 +234,7 @@ public void test_customActionExpand() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- dragHandleView.performAccessibilityAction(ACTION_EXPAND.getId(), /* args= */ null);
+ dragHandleView.performAccessibilityAction(ACTION_EXPAND.getId(), /* arguments= */ null);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -225,7 +251,7 @@ public void test_customActionHalfExpand() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- dragHandleView.performAccessibilityAction(getHalfExpandActionId(), /* args= */ null);
+ dragHandleView.performAccessibilityAction(getHalfExpandActionId(), /* arguments= */ null);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -241,7 +267,7 @@ public void test_customActionCollapse() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- dragHandleView.performAccessibilityAction(ACTION_COLLAPSE.getId(), /* args= */ null);
+ dragHandleView.performAccessibilityAction(ACTION_COLLAPSE.getId(), /* arguments= */ null);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -258,7 +284,7 @@ public void test_customActionDismiss() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- dragHandleView.performAccessibilityAction(ACTION_DISMISS.getId(), /* args= */ null);
+ dragHandleView.performAccessibilityAction(ACTION_DISMISS.getId(), /* arguments= */ null);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
diff --git a/lib/javatests/com/google/android/material/carousel/CarouselHelper.java b/lib/javatests/com/google/android/material/carousel/CarouselHelper.java
index 8641ca38291..a55b4fa4f75 100644
--- a/lib/javatests/com/google/android/material/carousel/CarouselHelper.java
+++ b/lib/javatests/com/google/android/material/carousel/CarouselHelper.java
@@ -457,7 +457,7 @@ static KeylineState getTestCenteredKeylineState() {
float smallMask = getKeylineMaskPercentage(smallSize, largeSize);
float mediumMask = getKeylineMaskPercentage(mediumSize, largeSize);
- return new KeylineState.Builder(450F, 1320F)
+ return new KeylineState.Builder(450F, 1320)
.addKeyline(5F, extraSmallMask, extraSmallSize)
.addKeylineRange(38F, smallMask, smallSize, 2)
.addKeyline(166F, mediumMask, mediumSize)
@@ -478,7 +478,7 @@ static KeylineState getTestCenteredVerticalKeylineState() {
float smallMask = getKeylineMaskPercentage(smallSize, largeSize);
float mediumMask = getKeylineMaskPercentage(mediumSize, largeSize);
- return new KeylineState.Builder(100F, 200F)
+ return new KeylineState.Builder(100F, 200)
.addKeyline(9F, smallMask, smallSize)
.addKeyline(25F, mediumMask, mediumSize)
.addKeyline(66F, 0F, largeSize, true)
diff --git a/lib/javatests/com/google/android/material/carousel/KeylineStateListTest.java b/lib/javatests/com/google/android/material/carousel/KeylineStateListTest.java
index d712fe24e87..769a10be6e7 100644
--- a/lib/javatests/com/google/android/material/carousel/KeylineStateListTest.java
+++ b/lib/javatests/com/google/android/material/carousel/KeylineStateListTest.java
@@ -238,7 +238,7 @@ public void testStartArrangementWithOutOfBoundsKeyline_shouldShiftEnd() {
};
KeylineState state =
- new KeylineState.Builder(40F, 100F)
+ new KeylineState.Builder(40F, 100)
.addAnchorKeyline(-10F, getKeylineMaskPercentage(20F, 40F), 20F)
.addKeyline(10F, getKeylineMaskPercentage(20F, 40F), 20F, false)
.addKeyline(40F, 0F, 40F, true)
diff --git a/lib/javatests/com/google/android/material/carousel/MultiBrowseCarouselStrategyTest.java b/lib/javatests/com/google/android/material/carousel/MultiBrowseCarouselStrategyTest.java
index 298b01bb4d5..65dc22e2e9f 100644
--- a/lib/javatests/com/google/android/material/carousel/MultiBrowseCarouselStrategyTest.java
+++ b/lib/javatests/com/google/android/material/carousel/MultiBrowseCarouselStrategyTest.java
@@ -95,6 +95,22 @@ public void testSmallContainer_shouldShowOneLargeItem() {
assertThat(keylineState.getKeylines().get(1).maskedItemSize).isEqualTo((float) carouselWidth);
}
+ @Test
+ public void testContainer_shouldShowOneLargeOneSmallItem() {
+ View view = createViewWithSize(ApplicationProvider.getApplicationContext(), 100, 400);
+ float minSmallItemSize =
+ view.getResources().getDimension(R.dimen.m3_carousel_small_item_size_min);
+ // Create a carousel that will fit at least one small item and one larger item
+ int carouselWidth = ((int) (minSmallItemSize * 2f)) + 1;
+ Carousel carousel = createCarouselWithWidth(carouselWidth);
+
+ MultiBrowseCarouselStrategy config = setupStrategy();
+ KeylineState keylineState = config.onFirstChildMeasuredWithMargins(carousel, view);
+
+ assertThat(keylineState.getKeylines()).hasSize(4);
+ assertThat(keylineState.getKeylines().get(2).maskedItemSize).isEqualTo(minSmallItemSize);
+ }
+
@Test
public void testKnownArrangementWithMediumItem_correctlyCalculatesKeylineLocations() {
float[] locOffsets = new float[] {-.5F, 100F, 300F, 464F, 556F, 584.5F};
diff --git a/lib/javatests/com/google/android/material/motion/MotionUtilsTest.java b/lib/javatests/com/google/android/material/motion/MotionUtilsTest.java
index 1cb0e7e6775..7bf03bd1284 100644
--- a/lib/javatests/com/google/android/material/motion/MotionUtilsTest.java
+++ b/lib/javatests/com/google/android/material/motion/MotionUtilsTest.java
@@ -19,8 +19,10 @@
import com.google.android.material.test.R;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import android.animation.TimeInterpolator;
+import android.content.Context;
import android.os.Build.VERSION_CODES;
import androidx.appcompat.app.AppCompatActivity;
import android.view.animation.AccelerateInterpolator;
@@ -29,6 +31,8 @@
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
import androidx.annotation.RequiresApi;
+import androidx.core.content.res.ResourcesCompat;
+import androidx.dynamicanimation.animation.SpringForce;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Rule;
import org.junit.Test;
@@ -51,6 +55,46 @@ public class MotionUtilsTest {
@Rule public final ExpectedException thrown = ExpectedException.none();
+ @Test
+ public void testResolvesThemeSpring() {
+ createActivityAndSetTheme(R.style.Theme_Material3_DayNight);
+ Context context = activityController.get().getApplicationContext();
+ float expectedStiffness = ResourcesCompat.getFloat(
+ context.getResources(), R.dimen.m3_sys_motion_standard_spring_fast_spatial_stiffness);
+ float expectedDampingRatio = ResourcesCompat.getFloat(
+ context.getResources(), R.dimen.m3_sys_motion_standard_spring_fast_spatial_damping);
+ SpringForce spring = MotionUtils.resolveThemeSpringForce(context,
+ R.attr.motionSpringFastSpatial, R.style.Motion_Material3_Spring_Standard_Fast_Spatial);
+
+ assertThat(spring.getStiffness()).isEqualTo(expectedStiffness);
+ assertThat(spring.getDampingRatio()).isEqualTo(expectedDampingRatio);
+ }
+
+ @Test
+ public void testAbsentThemeSpring_shouldResolveDefault() {
+ createActivityAndSetTheme(R.style.Theme_AppCompat_DayNight);
+ Context context = activityController.get().getApplicationContext();
+
+ SpringForce spring = MotionUtils.resolveThemeSpringForce(context,
+ R.attr.motionSpringFastSpatial, R.style.Motion_MyApp_Spring_Custom_Default);
+
+ assertThat(spring.getStiffness()).isEqualTo(1450f);
+ assertThat(spring.getDampingRatio()).isEqualTo(0.5f);
+ }
+
+ @Test
+ public void testPartialSpring_shouldThrow() {
+ createActivityAndSetTheme(R.style.Theme_Material3_DayNight_PartialSpring);
+ Context context = activityController.get().getApplicationContext();
+
+ IllegalArgumentException thrown = assertThrows(
+ IllegalArgumentException.class,
+ () -> MotionUtils.resolveThemeSpringForce(context, R.attr.motionSpringFastSpatial,
+ R.style.Motion_Material3_Spring_Standard_Fast_Spatial)
+ );
+ assertThat(thrown).hasMessageThat().contains("must have a damping");
+ }
+
@Test
public void testResolvesThemeInterpolator() {
assertThemeInterpolatorIsInstanceOf(
diff --git a/lib/javatests/com/google/android/material/motion/res/values/themes.xml b/lib/javatests/com/google/android/material/motion/res/values/themes.xml
index c96035710bc..f117b195b84 100644
--- a/lib/javatests/com/google/android/material/motion/res/values/themes.xml
+++ b/lib/javatests/com/google/android/material/motion/res/values/themes.xml
@@ -15,6 +15,17 @@
limitations under the License.
-->
+
+
+
+
-
diff --git a/testing/java/com/google/android/material/testapp/animation/build.gradle b/testing/java/com/google/android/material/testapp/animation/build.gradle
index fb6a88c2521..1880df59cdd 100644
--- a/testing/java/com/google/android/material/testapp/animation/build.gradle
+++ b/testing/java/com/google/android/material/testapp/animation/build.gradle
@@ -17,24 +17,19 @@
apply plugin: 'com.android.application'
dependencies {
- api compatibility("appcompat")
- api compatibility("core")
+ api libs.androidx.appcompat
+ api libs.androidx.core
api project(fromPath("lib"))
api project(fromPath("testing/java/com/google/android/material/testapp/base"))
- api 'androidx.multidex:multidex:2.0.0'
+ api libs.androidx.multidex
}
android {
+ namespace "com.google.android.material.testapp.animation"
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
- main.java.srcDirs = [ '.' ]
- main.java.excludes = [
- '**/build/**',
- ]
- // Only include things in this directory, not subdirectories
- main.java.includes = [ '*.java' ]
main.res.srcDirs = [ 'res' ]
}
defaultConfig {
diff --git a/testing/java/com/google/android/material/testapp/base/build.gradle b/testing/java/com/google/android/material/testapp/base/build.gradle
index b6131f8668e..1e91139a238 100644
--- a/testing/java/com/google/android/material/testapp/base/build.gradle
+++ b/testing/java/com/google/android/material/testapp/base/build.gradle
@@ -17,20 +17,22 @@
apply plugin: 'com.android.library'
dependencies {
- api compatibility("appcompat")
- api compatibility("core")
- api compatibility("fragment")
+ api libs.androidx.appcompat
+ api libs.androidx.core
+ api libs.androidx.fragment
}
android {
+ namespace "com.google.android.material.testapp.base"
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
- main.java.srcDirs = [ '.' ]
- main.java.excludes = [
- '**/build/**',
+ // Manually add src files. Gradle 8 doesn't work well with
+ // a flat source directory (src = [ '.' ]) where java source, a gradle
+ // file, and a build output directory are all in the same location.
+ main.java.srcDirs = [
+ 'BaseTestActivity.java',
+ 'RecreatableAppCompatActivity.java'
]
- // Only include things in this directory, not subdirectories
- main.java.includes = [ '*.java' ]
}
defaultConfig {
minSdkVersion 21
diff --git a/testing/java/com/google/android/material/testapp/build.gradle b/testing/java/com/google/android/material/testapp/build.gradle
index 7e61aa890d7..be7e5e3af27 100644
--- a/testing/java/com/google/android/material/testapp/build.gradle
+++ b/testing/java/com/google/android/material/testapp/build.gradle
@@ -2,17 +2,18 @@
apply plugin: 'com.android.application'
dependencies {
- api compatibility("annotation")
- api compatibility("appcompat")
+ api libs.androidx.annotation
+ api libs.androidx.appcompat
api project(fromPath("lib"))
api project(fromPath("testing/java/com/google/android/material/testapp/base"))
api project(fromPath("testing/java/com/google/android/material/testapp/custom"))
- api 'androidx.multidex:multidex:2.0.0'
+ api libs.androidx.multidex
}
android {
+ namespace "com.google.android.material.testapp"
defaultConfig {
multiDexEnabled true
minSdkVersion 21
@@ -21,12 +22,31 @@ android {
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
- main.java.srcDirs = [ '.' ]
- main.java.excludes = [
- '**/build/**',
+ // Manually add src files. Gradle 8 doesn't work well with
+ // a flat source directory (src = [ '.' ]) where java source, a gradle
+ // file, and a build output directory are all in the same location.
+ main.java.srcDirs = [
+ 'AppBarHorizontalScrollingActivity.java',
+ 'AppBarLayoutCollapsePinActivity.java',
+ 'AppBarWithScrollbarsActivity.java',
+ 'BottomNavigationViewActivity.java',
+ 'BottomSheetBehaviorActivity.java',
+ 'BottomSheetBehaviorWithInsetsActivity.java',
+ 'BottomSheetDialogActivity.java',
+ 'CoordinatorLayoutActivity.java',
+ 'DynamicCoordinatorLayoutActivity.java',
+ 'ExpandableTransformationActivity.java',
+ 'ExposedDropdownMenuActivity.java',
+ 'FloatingActionButtonActivity.java',
+ 'NavigationViewActivity.java',
+ 'SnackbarActivity.java',
+ 'SnackbarWithFabActivity.java',
+ 'SnackbarWithTranslucentNavBarActivity.java',
+ 'TabLayoutPoolingActivity.java',
+ 'TabLayoutWithViewPagerActivity.java',
+ 'TextInputLayoutActivity.java',
+ 'TextInputLayoutWithIconsActivity.java'
]
- // Only include things in this directory, not subdirectories
- main.java.includes = [ '*.java' ]
main.res.srcDirs = [ 'res' ]
}
diff --git a/testing/java/com/google/android/material/testapp/custom/build.gradle b/testing/java/com/google/android/material/testapp/custom/build.gradle
index fe48cfaf468..96854d7fe94 100644
--- a/testing/java/com/google/android/material/testapp/custom/build.gradle
+++ b/testing/java/com/google/android/material/testapp/custom/build.gradle
@@ -1,23 +1,28 @@
apply plugin: 'com.android.library'
dependencies {
- api compatibility("appcompat")
- api compatibility("core")
+ api libs.androidx.appcompat
+ api libs.androidx.core
api project(fromPath("lib"))
- implementation "com.google.errorprone:error_prone_annotations:${errorproneVersion}"
+ implementation libs.errorprone.annotations
}
android {
+ namespace "com.google.android.material.testapp.custom"
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
- main.java.srcDirs = [ '.' ]
- main.java.excludes = [
- '**/build/**',
+ // Manually add src files. Gradle 8 doesn't work well with
+ // a flat source directory (src = [ '.' ]) where java source, a gradle
+ // file, and a build output directory are all in the same location.
+ main.java.srcDirs = [
+ 'CustomSnackbar.java',
+ 'CustomSnackbarMainContent.java',
+ 'CustomTextView.java',
+ 'NavigationTestView.java',
+ 'TestFloatingBehavior.java'
]
- // Only include things in this directory, not subdirectories
- main.java.includes = [ '*.java' ]
main.res.srcDirs = [ 'res' ]
}
defaultConfig {
diff --git a/testing/java/com/google/android/material/testapp/theme/build.gradle b/testing/java/com/google/android/material/testapp/theme/build.gradle
index cc85fb968ef..c8cad724b58 100644
--- a/testing/java/com/google/android/material/testapp/theme/build.gradle
+++ b/testing/java/com/google/android/material/testapp/theme/build.gradle
@@ -17,29 +17,25 @@
apply plugin: 'com.android.application'
dependencies {
- api compatibility("appcompat")
- api compatibility("core")
+ api libs.androidx.appcompat
+ api libs.androidx.core
- /* api project(fromPath("lib/java/com/google/android/material/theme"))
- api project(fromPath("lib/java/com/google/android/material/button"))
- api project(fromPath("lib/java/com/google/android/material/radiobutton"))
- api project(fromPath("lib/java/com/google/android/material/checkbox"))
-*/
api project(fromPath("lib"))
api project(fromPath("testing/java/com/google/android/material/testapp/base"))
- api 'androidx.multidex:multidex:2.0.1'
+ api libs.androidx.multidex
}
android {
+ namespace "com.google.android.material.testapp.theme"
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
- main.java.srcDirs = [ '.' ]
- main.java.excludes = [
- '**/build/**',
+ // Manually add src files. Gradle 8 doesn't work well with
+ // a flat source directory (src = [ '.' ]) where java source, a gradle
+ // file, and a build output directory are all in the same location.
+ main.java.srcDirs = [
+ 'MaterialComponentsViewInflaterActivity.java'
]
- // Only include things in this directory, not subdirectories
- main.java.includes = [ '*.java' ]
main.res.srcDirs = [ 'res' ]
}
defaultConfig {
diff --git a/tests/build.gradle b/tests/build.gradle
index ce3ef91891b..256e3769d2c 100644
--- a/tests/build.gradle
+++ b/tests/build.gradle
@@ -1,15 +1,15 @@
apply plugin: 'com.android.test'
dependencies {
- implementation "androidx.test:core:${project.rootProject.ext.testRunnerVersion}"
- implementation "androidx.test:runner:${project.rootProject.ext.testRunnerVersion}"
- implementation "androidx.test:rules:${project.rootProject.ext.testRunnerVersion}"
- implementation "androidx.test.espresso:espresso-accessibility:${project.rootProject.ext.espressoVersion}"
- implementation "androidx.test.espresso:espresso-core:${project.rootProject.ext.espressoVersion}"
- implementation "androidx.test.espresso:espresso-contrib:${project.rootProject.ext.espressoVersion}"
- implementation "org.mockito:mockito-core:${project.rootProject.ext.mockitoCoreVersion}"
- implementation 'com.google.dexmaker:dexmaker:1.2'
- implementation 'com.google.dexmaker:dexmaker-mockito:1.2'
+ implementation libs.androidx.test.core
+ implementation libs.androidx.test.runner
+ implementation libs.androidx.test.rules
+ implementation libs.androidx.espresso.accessibility
+ implementation libs.androidx.espresso.core
+ implementation libs.androidx.espresso.contrib
+ implementation libs.mockito.core
+ implementation libs.dexmaker
+ implementation libs.dexmaker.mokito
}
android {
diff --git a/tests/javatests/com/google/android/material/appbar/AppBarWithDodgingTest.java b/tests/javatests/com/google/android/material/appbar/AppBarWithDodgingTest.java
index 1215b7ade07..90ed68361b4 100644
--- a/tests/javatests/com/google/android/material/appbar/AppBarWithDodgingTest.java
+++ b/tests/javatests/com/google/android/material/appbar/AppBarWithDodgingTest.java
@@ -43,8 +43,8 @@ public void testLeftDodge() throws Throwable {
final Rect fabRect = new Rect();
final Rect fab2Rect = new Rect();
- fab.getContentRect(fabRect);
- fab2.getContentRect(fab2Rect);
+ fab.getMeasuredContentRect(fabRect);
+ fab2.getMeasuredContentRect(fab2Rect);
// Our second FAB is configured to "dodge" the first one - to be displayed to the
// right of it
@@ -72,8 +72,8 @@ public void testRightDodge() throws Throwable {
final Rect fabRect = new Rect();
final Rect fab2Rect = new Rect();
- fab.getContentRect(fabRect);
- fab2.getContentRect(fab2Rect);
+ fab.getMeasuredContentRect(fabRect);
+ fab2.getMeasuredContentRect(fab2Rect);
// Our second FAB is configured to "dodge" the first one - to be displayed to the
// left of it
diff --git a/tests/javatests/com/google/android/material/datepicker/MaterialDatePickerPagesTest.java b/tests/javatests/com/google/android/material/datepicker/MaterialDatePickerPagesTest.java
index 0db0a612eef..5ac0ba7e6d8 100644
--- a/tests/javatests/com/google/android/material/datepicker/MaterialDatePickerPagesTest.java
+++ b/tests/javatests/com/google/android/material/datepicker/MaterialDatePickerPagesTest.java
@@ -15,8 +15,14 @@
*/
package com.google.android.material.datepicker;
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
+import static androidx.test.espresso.matcher.ViewMatchers.withTagValue;
import static com.google.android.material.datepicker.MaterialDatePickerTestUtils.findFirstVisibleItem;
import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
@@ -141,4 +147,44 @@ public void accessibility_daySelection_notScrollable() {
assertFalse(nodeInfo.isScrollable());
}
+
+ @Test
+ public void previousButtonDisabledAtStartBoundary() {
+ MaterialDatePickerTestUtils.clickPrev();
+ onView(withTagValue(equalTo(MaterialCalendar.NAVIGATION_PREV_TAG)))
+ .check(matches(not(isEnabled())));
+ }
+
+ @Test
+ public void nextButtonDisabledAtEndBoundary() {
+ MaterialDatePickerTestUtils.clickNext();
+ MaterialDatePickerTestUtils.clickNext();
+ MaterialDatePickerTestUtils.clickNext();
+ onView(withTagValue(equalTo(MaterialCalendar.NAVIGATION_NEXT_TAG)))
+ .check(matches(not(isEnabled())));
+ }
+
+ @Test
+ public void nextButtonEnabledAtStartBoundary() {
+ MaterialDatePickerTestUtils.clickPrev();
+ onView(withTagValue(equalTo(MaterialCalendar.NAVIGATION_NEXT_TAG)))
+ .check(matches(isEnabled()));
+ }
+
+ @Test
+ public void previousButtonEnabledAtEndBoundary() {
+ MaterialDatePickerTestUtils.clickNext();
+ MaterialDatePickerTestUtils.clickNext();
+ MaterialDatePickerTestUtils.clickNext();
+ onView(withTagValue(equalTo(MaterialCalendar.NAVIGATION_PREV_TAG)))
+ .check(matches(isEnabled()));
+ }
+
+ @Test
+ public void navigationButtonsEnabledInMiddleOfBoundary() {
+ onView(withTagValue(equalTo(MaterialCalendar.NAVIGATION_NEXT_TAG)))
+ .check(matches(isEnabled()));
+ onView(withTagValue(equalTo(MaterialCalendar.NAVIGATION_NEXT_TAG)))
+ .check(matches(isEnabled()));
+ }
}
diff --git a/tests/javatests/com/google/android/material/datepicker/TestBackgroundHighlightDecorator.java b/tests/javatests/com/google/android/material/datepicker/TestBackgroundHighlightDecorator.java
index 1f6aa421631..0c60c07f775 100644
--- a/tests/javatests/com/google/android/material/datepicker/TestBackgroundHighlightDecorator.java
+++ b/tests/javatests/com/google/android/material/datepicker/TestBackgroundHighlightDecorator.java
@@ -17,6 +17,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
+import android.graphics.Color;
import android.os.Parcel;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
@@ -73,7 +74,7 @@ private int getBackgroundHighlightColor(Context context) {
@ColorInt
private int getTextHighlightColor(Context context) {
- return MaterialColors.getColor(context, R.attr.colorOnTertiary, R.attr.colorControlNormal);
+ return MaterialColors.getColor(context, R.attr.colorOnTertiary, Color.BLACK);
}
@ColorInt
diff --git a/tests/javatests/com/google/android/material/floatingactionbutton/FloatingActionButtonTest.java b/tests/javatests/com/google/android/material/floatingactionbutton/FloatingActionButtonTest.java
index 2eb31924265..7069c76b554 100644
--- a/tests/javatests/com/google/android/material/floatingactionbutton/FloatingActionButtonTest.java
+++ b/tests/javatests/com/google/android/material/floatingactionbutton/FloatingActionButtonTest.java
@@ -236,7 +236,7 @@ public void testClickableTouchAndDragOffView() {
final int[] xy = new int[2];
fab.getLocationOnScreen(xy);
final Rect rect = new Rect();
- fab.getContentRect(rect);
+ fab.getMeasuredContentRect(rect);
return new float[] {xy[0] + rect.centerX(), xy[1] + rect.centerY()};
},
diff --git a/tests/javatests/com/google/android/material/snackbar/SnackbarTest.java b/tests/javatests/com/google/android/material/snackbar/SnackbarTest.java
index c99cc981d36..dfc9773900d 100644
--- a/tests/javatests/com/google/android/material/snackbar/SnackbarTest.java
+++ b/tests/javatests/com/google/android/material/snackbar/SnackbarTest.java
@@ -29,7 +29,6 @@
import static com.google.android.material.testutils.TestUtilsActions.setLayoutDirection;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.AllOf.allOf;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -400,17 +399,6 @@ public void testMultipleCallbacksWithRemoval() throws Throwable {
.onDismissed(snackbar, BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_MANUAL);
}
- @Test
- public void testAccessibilityPaneTitle() throws Throwable {
- final Snackbar snackbar =
- Snackbar.make(coordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
- .setAction(ACTION_TEXT, mock(View.OnClickListener.class));
- SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar);
- assertEquals(
- snackbar.getContext().getString(R.string.snackbar_accessibility_pane_title),
- ViewCompat.getAccessibilityPaneTitle(snackbar.getView()).toString());
- }
-
@Test
public void testDefaultContext_usesAppCompat() throws Throwable {
final Snackbar snackbar =
diff --git a/tests/javatests/com/google/android/material/testutils/TestUtilsMatchers.java b/tests/javatests/com/google/android/material/testutils/TestUtilsMatchers.java
index f7791d1b7b4..7d30dc5fefb 100644
--- a/tests/javatests/com/google/android/material/testutils/TestUtilsMatchers.java
+++ b/tests/javatests/com/google/android/material/testutils/TestUtilsMatchers.java
@@ -330,7 +330,7 @@ public boolean matchesSafely(final View view) {
// Since the FAB background is round, and may contain the shadow, we'll look at
// just the center half rect of the content area
final Rect area = new Rect();
- fab.getContentRect(area);
+ fab.getMeasuredContentRect(area);
final int rectHeightQuarter = area.height() / 4;
final int rectWidthQuarter = area.width() / 4;
@@ -404,7 +404,7 @@ public boolean matchesSafely(final View view) {
final FloatingActionButton fab = (FloatingActionButton) view;
final Rect area = new Rect();
- fab.getContentRect(area);
+ fab.getMeasuredContentRect(area);
if (area.height() != size) {
failedCheckDescription =
@@ -439,7 +439,7 @@ public boolean matchesSafely(final View view) {
final ViewGroup parent = (ViewGroup) view.getParent();
final Rect area = new Rect();
- fab.getContentRect(area);
+ fab.getMeasuredContentRect(area);
final int absGravity =
GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(view));
diff --git a/tests/javatests/com/google/android/material/theme/build.gradle b/tests/javatests/com/google/android/material/theme/build.gradle
index 82013d3b53b..b36c0412949 100644
--- a/tests/javatests/com/google/android/material/theme/build.gradle
+++ b/tests/javatests/com/google/android/material/theme/build.gradle
@@ -17,20 +17,20 @@
apply plugin: 'com.android.test'
dependencies {
- implementation "androidx.test.ext:junit:1.1.1"
- implementation "androidx.test:runner:${project.rootProject.ext.testRunnerVersion}"
- implementation "androidx.test:rules:${project.rootProject.ext.testRunnerVersion}"
- implementation "androidx.test.espresso:espresso-core:${project.rootProject.ext.espressoVersion}"
- implementation "androidx.test.espresso:espresso-contrib:${project.rootProject.ext.espressoVersion}"
- implementation "org.mockito:mockito-core:${project.rootProject.ext.mockitoCoreVersion}"
- implementation 'com.google.dexmaker:dexmaker:1.2'
- implementation 'com.google.dexmaker:dexmaker-mockito:1.2'
- implementation "com.google.truth:truth:${project.rootProject.ext.truthVersion}"
- implementation compatibility("annotation")
- implementation compatibility("appcompat")
- implementation compatibility("core")
+ implementation libs.junit
+ implementation libs.androidx.test.runner
+ implementation libs.androidx.test.rules
+ implementation libs.androidx.espresso.core
+ implementation libs.androidx.espresso.contrib
+ implementation libs.mockito.core
+ implementation libs.dexmaker
+ implementation libs.dexmaker.mokito
+ implementation libs.truth
+ implementation libs.androidx.annotation
+ implementation libs.androidx.appcompat
+ implementation libs.androidx.core
- api 'androidx.multidex:multidex:2.0.1'
+ api libs.androidx.multidex
}
android {