Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 152 additions & 2 deletions lib/java/com/google/android/material/button/MaterialButton.java
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,13 @@ enum WidthChangeDirection {
BOTH
}

enum HeightChangeDirection {
NONE,
TOP,
BOTTOM,
BOTH
}

private static final String LOG_TAG = "MaterialButton";

private static final int DEF_STYLE_RES = R.style.Widget_MaterialComponents_Button;
Expand Down Expand Up @@ -253,8 +260,11 @@ enum WidthChangeDirection {

private int orientation = UNSET;
private float originalWidth = UNSET;
private float originalHeight = UNSET;
@Px private int originalPaddingStart = UNSET;
@Px private int originalPaddingTop = UNSET;
@Px private int originalPaddingEnd = UNSET;
@Px private int originalPaddingBottom = UNSET;

@Nullable private LayoutParams originalLayoutParams;

Expand All @@ -265,12 +275,18 @@ enum WidthChangeDirection {

// Fields for size morphing.
@Px int allowedWidthDecrease = UNSET;
@Px int allowedHeightDecrease = UNSET;
@Nullable StateListSizeChange sizeChange;
@Px int widthChangeMax;
@Px int heightChangeMax;
private WidthChangeDirection widthChangeDirection = WidthChangeDirection.BOTH;
private HeightChangeDirection heightChangeDirection = HeightChangeDirection.BOTH;
private float displayedWidthIncrease;
private float displayedWidthDecrease;
private float displayedHeightIncrease;
private float displayedHeightDecrease;
@Nullable private SpringAnimation widthIncreaseSpringAnimation;
@Nullable private SpringAnimation heightIncreaseSpringAnimation;

public MaterialButton(@NonNull Context context) {
this(context, null /* attrs */);
Expand Down Expand Up @@ -332,11 +348,16 @@ public MaterialButton(@NonNull Context context, @Nullable AttributeSet attrs, in
updateIcon(/* needsIconReset= */ icon != null);
}

private void initializeSizeAnimation() {
private void initializeWidthAnimation() {
widthIncreaseSpringAnimation = new SpringAnimation(this, WIDTH_INCREASE);
widthIncreaseSpringAnimation.setSpring(createSpringForce());
}

private void initializeHeightAnimation() {
heightIncreaseSpringAnimation = new SpringAnimation(this, HEIGHT_INCREASE);
heightIncreaseSpringAnimation.setSpring(createSpringForce());
}

private SpringForce createSpringForce() {
return MotionUtils.resolveThemeSpringForce(
getContext(),
Expand Down Expand Up @@ -552,6 +573,7 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
if (orientation != curOrientation) {
orientation = curOrientation;
originalWidth = UNSET;
originalHeight = UNSET;
}
if (originalWidth == UNSET) {
originalWidth = getMeasuredWidth();
Expand All @@ -568,6 +590,21 @@ && getParent() instanceof MaterialButtonGroup
setLayoutParams(newLayoutParams);
}
}
if (originalHeight == UNSET) {
originalHeight = getMeasuredHeight();
// The height morph leverage the height of the layout params. However, it's not available if
// layout_weight is used. We need to hardcode the height here. The original layout params will
// be preserved for the correctness of distribution when buttons are added or removed into the
// group programmatically.
if (originalLayoutParams == null
&& getParent() instanceof MaterialButtonGroup
&& ((MaterialButtonGroup) getParent()).getButtonSizeChange() != null) {
originalLayoutParams = (LayoutParams) getLayoutParams();
LayoutParams newLayoutParams = new LayoutParams(originalLayoutParams);
newLayoutParams.height = (int) originalHeight;
setLayoutParams(newLayoutParams);
}
}

if (allowedWidthDecrease == UNSET) {
int localIconSizeAndPadding =
Expand All @@ -576,13 +613,26 @@ && getParent() instanceof MaterialButtonGroup
: getIconPadding() + (iconSize == 0 ? icon.getIntrinsicWidth() : iconSize);
allowedWidthDecrease = getMeasuredWidth() - getTextLayoutWidth() - localIconSizeAndPadding;
}
if (allowedHeightDecrease == UNSET) {
int localIconSizeAndPadding =
icon == null
? 0
: getIconPadding() + (iconSize == 0 ? icon.getIntrinsicHeight() : iconSize);
allowedHeightDecrease = getMeasuredHeight() - getTextLayoutHeight() - localIconSizeAndPadding;
}

if (originalPaddingStart == UNSET) {
originalPaddingStart = getPaddingStart();
}
if (originalPaddingTop == UNSET) {
originalPaddingTop = getPaddingTop();
}
if (originalPaddingEnd == UNSET) {
originalPaddingEnd = getPaddingEnd();
}
if (originalPaddingBottom == UNSET) {
originalPaddingBottom = getPaddingBottom();
}
isInHorizontalButtonGroup = isInHorizontalButtonGroup();
}

Expand All @@ -591,6 +641,7 @@ void recoverOriginalLayoutParams() {
setLayoutParams(originalLayoutParams);
originalLayoutParams = null;
originalWidth = UNSET;
originalHeight = UNSET;
}
}

Expand All @@ -600,6 +651,12 @@ public void setWidth(@Px int pixels) {
super.setWidth(pixels);
}

@Override
public void setHeight(@Px int pixels) {
originalHeight = UNSET;
super.setHeight(pixels);
}

@Override
protected void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
super.onTextChanged(charSequence, i, i1, i2);
Expand Down Expand Up @@ -764,6 +821,16 @@ private int getTextLayoutWidth() {
return (int) ceil(maxWidth);
}

private int getTextLayoutHeight() {
float maxHeight = 0;
int lineCount = getLineCount();
for (int line = 0; line < lineCount; line++) {
int height = getLayout().getLineBottom(line) - getLayout().getLineTop(line);
maxHeight = max(maxHeight, height);
}
return (int) ceil(maxHeight);
}

private int getTextHeight() {
if (getLineCount() > 1) {
// If it's multi-line, return the internal text layout's height.
Expand Down Expand Up @@ -1528,9 +1595,14 @@ private void maybeAnimateSize(boolean skipAnimation) {
if (sizeChange == null) {
return;
}

if (widthIncreaseSpringAnimation == null) {
initializeSizeAnimation();
initializeWidthAnimation();
}
if (heightIncreaseSpringAnimation == null) {
initializeHeightAnimation();
}

if (isInHorizontalButtonGroup) {
// Animate width.
int widthChange =
Expand All @@ -1544,6 +1616,19 @@ private void maybeAnimateSize(boolean skipAnimation) {
if (skipAnimation) {
widthIncreaseSpringAnimation.skipToEnd();
}
} else {
// Animate height.
int heightChange =
min(
heightChangeMax, // TODO
sizeChange
.getSizeChangeForState(getDrawableState())
.heightChange
.getChange(getHeight()));
heightIncreaseSpringAnimation.animateToFinalPosition(heightChange);
if (skipAnimation) {
heightIncreaseSpringAnimation.skipToEnd();
}
}
}

Expand Down Expand Up @@ -1614,6 +1699,48 @@ void setDisplayedWidthDecrease(int widthDecrease) {
invalidate();
}

void setHeightChangeMax(@Px int heightChangeMax) {
if (this.heightChangeMax != heightChangeMax) {
this.heightChangeMax = heightChangeMax;
maybeAnimateSize(/* skipAnimation= */ true);
}
}

void setHeightChangeDirection(@NonNull HeightChangeDirection heightChangeDirection) {
if (this.heightChangeDirection != heightChangeDirection) {
this.heightChangeDirection = heightChangeDirection;
maybeAnimateSize(/* skipAnimation= */ true);
}
}

@Px
int getAllowedHeightDecrease() {
return allowedHeightDecrease;
}

private float getDisplayedHeightIncrease() {
return displayedHeightIncrease;
}

private void setDisplayedHeightIncrease(float heightIncrease) {
if (displayedHeightIncrease != heightIncrease) {
displayedHeightIncrease = heightIncrease;
updatePaddingsAndSizeForHeightAnimation();
invalidate();
// Report width changed to the parent group.
if (getParent() instanceof MaterialButtonGroup) {
((MaterialButtonGroup) getParent())
.onButtonHeightChanged(this, (int) displayedHeightIncrease);
}
}
}

void setDisplayedHeightDecrease(int heightDecrease) {
displayedHeightDecrease = min(heightDecrease, allowedHeightDecrease);
updatePaddingsAndSizeForHeightAnimation();
invalidate();
}

/**
* Sets whether to enable the optical center feature.
*
Expand Down Expand Up @@ -1667,6 +1794,17 @@ private void updatePaddingsAndSizeForWidthAnimation() {
getPaddingBottom());
}

private void updatePaddingsAndSizeForHeightAnimation() {
int heightChange = (int) (displayedHeightIncrease - displayedHeightDecrease);
int paddingTopChange = heightChange / 2;
getLayoutParams().height = (int) (originalHeight + heightChange);
setPaddingRelative(
originalPaddingStart,
originalPaddingTop + paddingTopChange,
originalPaddingEnd,
originalPaddingBottom + heightChange - paddingTopChange);
}

private int getOpticalCenterShift() {
if (opticalCenterEnabled && isInHorizontalButtonGroup) {
MaterialShapeDrawable materialShapeDrawable = materialButtonHelper.getMaterialShapeDrawable();
Expand All @@ -1691,6 +1829,18 @@ public void setValue(MaterialButton button, float value) {
button.setDisplayedWidthIncrease(value);
}
};
private static final FloatPropertyCompat<MaterialButton> HEIGHT_INCREASE =
new FloatPropertyCompat<MaterialButton>("heightIncrease") {
@Override
public float getValue(MaterialButton button) {
return button.getDisplayedHeightIncrease();
}

@Override
public void setValue(MaterialButton button, float value) {
button.setDisplayedHeightIncrease(value);
}
};

static class SavedState extends AbsSavedState {

Expand Down
Loading