Skip to content

[ShapeableImageView] Fix padding bugs #2280

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
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
110 changes: 76 additions & 34 deletions lib/java/com/google/android/material/imageview/ShapeableImageView.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@
import android.graphics.RectF;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.AppCompatImageView;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
Expand All @@ -48,6 +46,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.AppCompatImageView;
import com.google.android.material.resources.MaterialResources;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.shape.ShapeAppearanceModel;
Expand Down Expand Up @@ -169,15 +169,15 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
return;
}

if (VERSION.SDK_INT > 19 && !isLayoutDirectionResolved()) {
if (VERSION.SDK_INT >= 19 && !isLayoutDirectionResolved()) {
return;
}

hasAdjustedPaddingAfterLayoutDirectionResolved = true;

// Update the super padding to be the combined `android:padding` and
// `app:contentPadding`, keeping with ShapeableImageView's internal padding contract:
if (VERSION.SDK_INT >= 21 && (isPaddingRelative() || isContentPaddingRelative())) {
if (VERSION.SDK_INT >= 17 && (isPaddingRelative() || isContentPaddingRelative())) {
setPaddingRelative(
super.getPaddingStart(),
super.getPaddingTop(),
Expand Down Expand Up @@ -219,13 +219,17 @@ public void setContentPadding(
startContentPadding = UNDEFINED_PADDING;
endContentPadding = UNDEFINED_PADDING;

// Super padding is equal to background padding + content padding. Adjust the content padding
// portion of the super padding here:
super.setPadding(
super.getPaddingLeft() - leftContentPadding + left,
super.getPaddingTop() - topContentPadding + top,
super.getPaddingRight() - rightContentPadding + right,
super.getPaddingBottom() - bottomContentPadding + bottom);
// If onMeasure hasn't adjusted padding yet, wait for it to be set to avoid double-applying the
// content padding in onMeasure:
if (hasAdjustedPaddingAfterLayoutDirectionResolved) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this affect API >= 19?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, hasAdjustedPaddingAfterLayoutDirectionResolved doesn't get set until after onMeasure runs after isLayoutDirectionResolved (which is an API 19+ method) becomes true. (So really it affects 19+ more than it affects pre-19.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Don't you need to call setContentPadding() again when hasAdjustedPaddingAfterLayoutDirectionResolved is set to true? So the previous set content paddings can be applied?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. setContentPadding does two things:

  1. It calls super.setPadding according to this class's internal padding contract. This is skipped if this flag is not set, but super.setPadding is also called immediately after the flag is set, so this call is covered either way.
  2. It sets the private contentPadding variables, regardless of whether that flag is set.

The private contentPadding variables are only used in the getContentPadding and getPadding getters. updateShapeMask uses the getPadding getters to set the shape size, so now I wonder if I need to force a call to updateShapeMask immediately after that flag is set too...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good catch. I think onSizeChanged() will be called after setting paddings, right? That should be fine I guess.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think onSizeChanged is not called in that case actually. I'll need to try to force an error this way to be sure, and add a related test if possible

// Super padding is equal to background padding + content padding. Adjust the content padding
// portion of the super padding here:
super.setPadding(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like there might be a bug if content padding are set twice, and the first time hasAdjustedPaddingAfterLayoutDirectionResolved is false and the second time it's true.

At the second call, super's paddings are not actually updated with content paddings yet, so subtracting the content paddings will causes extra paddings being subtracted. Can you also add test cases for this situation after you fix it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At your second call to setContentPadding, super's paddings have been updated, because setPadding is called in onMeasure immediately after hasAdjust... was set, and setPadding then called super.setPadding with the added differences.

In any case I'll look to add tests that check this.

super.getPaddingLeft() - leftContentPadding + left,
super.getPaddingTop() - topContentPadding + top,
super.getPaddingRight() - rightContentPadding + right,
super.getPaddingBottom() - bottomContentPadding + bottom);
}

leftContentPadding = left;
topContentPadding = top;
Expand All @@ -244,14 +248,20 @@ public void setContentPadding(
@RequiresApi(17)
public void setContentPaddingRelative(
@Dimension int start, @Dimension int top, @Dimension int end, @Dimension int bottom) {
// Super padding is equal to background padding + content padding. Adjust the content padding
// portion of the super padding here:
super.setPaddingRelative(
super.getPaddingStart() - getContentPaddingStart() + start,
super.getPaddingTop() - topContentPadding + top,
super.getPaddingEnd() - getContentPaddingEnd() + end,
super.getPaddingBottom() - bottomContentPadding + bottom);
// If onMeasure hasn't adjusted padding yet, wait for it to be set to avoid double-applying the
// content padding in onMeasure:
if (hasAdjustedPaddingAfterLayoutDirectionResolved) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

// Super padding is equal to background padding + content padding. Adjust the content padding
// portion of the super padding here:
super.setPaddingRelative(
super.getPaddingStart() - getContentPaddingStart() + start,
super.getPaddingTop() - topContentPadding + top,
super.getPaddingEnd() - getContentPaddingEnd() + end,
super.getPaddingBottom() - bottomContentPadding + bottom);
}

startContentPadding = start;
endContentPadding = end;
leftContentPadding = isRtl() ? end : start;
topContentPadding = top;
rightContentPadding = isRtl() ? start : end;
Expand Down Expand Up @@ -364,11 +374,15 @@ private boolean isRtl() {
@Override
public void setPadding(
@Dimension int left, @Dimension int top, @Dimension int right, @Dimension int bottom) {
super.setPadding(
left + getContentPaddingLeft(),
top + getContentPaddingTop(),
right + getContentPaddingRight(),
bottom + getContentPaddingBottom());
if (hasAdjustedPaddingAfterLayoutDirectionResolved) {
super.setPadding(
left + getContentPaddingLeft(),
top + getContentPaddingTop(),
right + getContentPaddingRight(),
bottom + getContentPaddingBottom());
} else {
super.setPadding(left, top, right, bottom);
}
}

/**
Expand All @@ -383,11 +397,15 @@ public void setPadding(
@Override
public void setPaddingRelative(
@Dimension int start, @Dimension int top, @Dimension int end, @Dimension int bottom) {
super.setPaddingRelative(
start + getContentPaddingStart(),
top + getContentPaddingTop(),
end + getContentPaddingEnd(),
bottom + getContentPaddingBottom());
if (hasAdjustedPaddingAfterLayoutDirectionResolved) {
super.setPaddingRelative(
start + getContentPaddingStart(),
top + getContentPaddingTop(),
end + getContentPaddingEnd(),
bottom + getContentPaddingBottom());
} else {
super.setPaddingRelative(start, top, end, bottom);
}
}

/**
Expand All @@ -398,7 +416,11 @@ public void setPaddingRelative(
@Override
@Dimension
public int getPaddingBottom() {
return super.getPaddingBottom() - getContentPaddingBottom();
if (hasAdjustedPaddingAfterLayoutDirectionResolved) {
return super.getPaddingBottom() - getContentPaddingBottom();
} else {
return super.getPaddingBottom();
}
}

/**
Expand All @@ -409,7 +431,11 @@ public int getPaddingBottom() {
@Override
@Dimension
public int getPaddingEnd() {
return super.getPaddingEnd() - getContentPaddingEnd();
if (hasAdjustedPaddingAfterLayoutDirectionResolved) {
return super.getPaddingEnd() - getContentPaddingEnd();
} else {
return super.getPaddingEnd();
}
}

/**
Expand All @@ -420,7 +446,11 @@ public int getPaddingEnd() {
@Override
@Dimension
public int getPaddingLeft() {
return super.getPaddingLeft() - getContentPaddingLeft();
if (hasAdjustedPaddingAfterLayoutDirectionResolved) {
return super.getPaddingLeft() - getContentPaddingLeft();
} else {
return super.getPaddingLeft();
}
}

/**
Expand All @@ -431,7 +461,11 @@ public int getPaddingLeft() {
@Override
@Dimension
public int getPaddingRight() {
return super.getPaddingRight() - getContentPaddingRight();
if (hasAdjustedPaddingAfterLayoutDirectionResolved) {
return super.getPaddingRight() - getContentPaddingRight();
} else {
return super.getPaddingRight();
}
}

/**
Expand All @@ -442,7 +476,11 @@ public int getPaddingRight() {
@Override
@Dimension
public int getPaddingStart() {
return super.getPaddingStart() - getContentPaddingStart();
if (hasAdjustedPaddingAfterLayoutDirectionResolved) {
return super.getPaddingStart() - getContentPaddingStart();
} else {
return super.getPaddingStart();
}
}

/**
Expand All @@ -453,7 +491,11 @@ public int getPaddingStart() {
@Override
@Dimension
public int getPaddingTop() {
return super.getPaddingTop() - getContentPaddingTop();
if (hasAdjustedPaddingAfterLayoutDirectionResolved) {
return super.getPaddingTop() - getContentPaddingTop();
} else {
return super.getPaddingTop();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.material.imageview">

<uses-sdk
tools:overrideLibrary="androidx.test.core"/>

<application/>
</manifest>
Loading