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

Conversation

drewhamilton
Copy link
Contributor

Thanks for starting a pull request on Material Components!

Don't forget:

  • Identify the component the PR relates to in brackets in the title.
    [Buttons] Updated documentation
  • Link to GitHub issues it solves. closes #1234
  • Sign the CLA bot. You can do this once the pull request is opened.

Fixes #2063.

Ensures that content padding values are not propagated to their respective super padding values until after onMeasure has completed with isLayoutDirectionResolved() == true at least once. This was already the case inside onMeasure, but not the case in setContentPadding and getPadding* methods.

The internal logic required to make this work is hard to reason about, so I added ShapeableImageViewTest to test that both types of padding work as expected on multiple Android SDKs.

@google-cla google-cla bot added the cla: yes label Jul 1, 2021
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.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

if (hasAdjustedPaddingAfterLayoutDirectionResolved) {
// 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.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.

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

private static final Class<CutCornerTreatment> CUT_CORNER_FAMILY_CLASS = CutCornerTreatment.class;

// Valid measureSpec values copied from a debugging session:
private static final int WIDTH_MEASURE_SPEC = 0x4000_03da;
Copy link
Contributor

Choose a reason for hiding this comment

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

Q) Can't we just use UNSPECIFIED here?

forceResolveLayoutDirection(imageView);
imageView.onMeasure(WIDTH_MEASURE_SPEC, HEIGHT_MEASURE_SPEC);

assertThat(imageView.getContentPaddingStart()).isEqualTo(5);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we also add tests to check if super.getPaddingTop/Bottom/Start/End() returns correct values?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[ShapeableImageView] contentPadding not working when set via Kotlin
2 participants