Skip to content
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

[ShapeableImageView] contentPadding not working when set via Kotlin #2063

Open
seth-gravy opened this issue Feb 11, 2021 · 9 comments · May be fixed by #2280 or #2609
Open

[ShapeableImageView] contentPadding not working when set via Kotlin #2063

seth-gravy opened this issue Feb 11, 2021 · 9 comments · May be fixed by #2280 or #2609

Comments

@seth-gravy
Copy link

Description:
When setting contentPadding on a ShapeableImageView in code (Kotlin or Java), it does not work as expected. Setting app:contentPadding via XML works fine.

Expected behavior:
Both elements should match and be like the top element.
image

Source code:

MainActivity.kt

package com.example.bug

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.TypedValue
import com.google.android.material.imageview.ShapeableImageView

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val px = TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP,
            8f,
            resources.displayMetrics
        ).toInt()

        findViewById<ShapeableImageView>(R.id.kotlin)?.setContentPadding(px, px, px, px)
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.google.android.material.imageview.ShapeableImageView
        android:id="@+id/xml"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="@color/black"
        app:contentPadding="8dp"
        app:layout_constraintBottom_toTopOf="@id/kotlin"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:shapeAppearanceOverlay="@style/CircleImageView"
        app:srcCompat="@color/teal_700" />

    <com.google.android.material.imageview.ShapeableImageView
        android:id="@+id/kotlin"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="@color/black"
        app:shapeAppearanceOverlay="@style/CircleImageView"
        app:srcCompat="@color/teal_700"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/xml"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="CircleImageView">
        <item name="cornerFamily">rounded</item>
        <item name="cornerSize">50%</item>
    </style>
</resources>

Android API version: 30

Material Library version: 1.3.0

Device: Pixel 3 and others

@seth-gravy seth-gravy added the bug label Feb 11, 2021
@seth-gravy
Copy link
Author

Likely related to #1871
CC: @drewhamilton @wcshi

@drewhamilton
Copy link
Contributor

Ouch, yep I can reproduce this. ShapeableImageView's onMeasure override is causing this if setContentPadding is called before the view is measured and its layout direction is resolved.

For now you can work around it by only calling setContentPadding after the view is measured and its layout direction is resolved. I'm seeing the expected behavior by wrapping the call in a post:

val imageView = findViewById<ShapeableImageView>(R.id.kotlin)
imageView.post { imageView.setContentPadding(px, px, px, px) }
@seth-gravy
Copy link
Author

Thank you for that solution; it works in simple cases but doesn't work in cases where I need to set both contentPadding and padding. For now I just have two buttons stacked and I show/hide them as the various padding values are needed.

@rs-shivani-gupta
Copy link

rs-shivani-gupta commented Jun 30, 2021

I'm also facing same issue when i use content padding via code (kotlin/java), it adds padding around the background as well. Any update on this? I need to use content padding along with padding for stroke which actually cuts the edges of view.
@drewhamilton

@drewhamilton
Copy link
Contributor

@rs-shivani-gupta Does the workaround posted above not work for you?

I think I can fix this actually, gonna take me a bit to write all the test cases though.

@rs-shivani-gupta
Copy link

@drewhamilton that worked for me, hoping that this would not break something else.

@J-Swift
Copy link

J-Swift commented Mar 3, 2022

I think I'm running into this. I see your PR is still on hold @drewhamilton, any idea why?

@drewhamilton
Copy link
Contributor

@J-Swift Sorry, I took a long hiatus and changed jobs. My PR needs tests and to address any other review comments which I don't remember the details of.

Life is busy right now so I don't know when I'll get to it – if you want to take a stab at it I wouldn't mind.

@J-Swift
Copy link

J-Swift commented Mar 4, 2022

@drewhamilton no worries, I have no expectations on it. I dont have the bandwidth for it either honestly, I can mostly work around it for now. Appreciate you taking the time to put in the PR and document things here.

@jpmcosta jpmcosta linked a pull request Mar 21, 2022 that will close this issue
jonathanpeppers added a commit to jonathanpeppers/maui that referenced this issue Nov 9, 2023
Context: a270ebd
Context: 1bbe79d
Context: material-components/material-components-android#2063

Reviewing a GC dump of the device tests, I noticed a `System.Action`
keeping the `ImageButton` alive:

    Microsoft.Maui.Controls.ImageButton
        System.Action
            Java.Lang.Thread.RunnableImplementor

So next, I looked for `System.Action` and found the path on the
`ReferencedTypes` tab:

    System.Action
        Microsoft.Maui.Platform.ImageButtonExtensions.[]c__DisplayClass4_0
            Google.Android.Material.ImageView.ShapeableImageView
            Microsoft.Maui.Controls.ImageButton

Which led me to the code:

    public static async void UpdatePadding(this ShapeableImageView platformButton, IImageButton imageButton)
    {
        platformButton.SetContentPadding(imageButton);
        platformButton.Post(() =>
        {
            platformButton.SetContentPadding(imageButton);
        });
        platformButton.SetContentPadding(imageButton);
    }

?!? Why is this code calling `SetContentPadding` three times?

Reviewing the commit history:

* a270ebd
* 1bbe79d
* material-components/material-components-android#2063

I could comment out the code and the leak is solved, but I found I could
also change the code to use `await Task.Yield()` for the same result.
jonathanpeppers added a commit to dotnet/maui that referenced this issue Nov 10, 2023
* [ios] fix memory leak in `ImageButton`

Context: #18365

Adding a parameter to the test:

    [Theory("Handler Does Not Leak")]
    [InlineData(typeof(ImageButton))]
    public async Task HandlerDoesNotLeak(Type type)

Shows a memory leak in `ImageButton`, caused by the cycle

* `ImageButtonHandler` ->
* `UIButton` events like `TouchUpInside` ->
* `ImageButtonHandler`

I could solve this problem by creating a `ImageButtonProxy` class -- the
same pattern I've used in other PRs to avoid cycles. This makes an
intermediate type to handle the events and breaks the cycle.

Still thinking if the analyzer could have caught this, issue filed at:

jonathanpeppers/memory-analyzers#12

* [android] fix memory leak in `ImageButton`

Context: a270ebd
Context: 1bbe79d
Context: material-components/material-components-android#2063

Reviewing a GC dump of the device tests, I noticed a `System.Action`
keeping the `ImageButton` alive:

    Microsoft.Maui.Controls.ImageButton
        System.Action
            Java.Lang.Thread.RunnableImplementor

So next, I looked for `System.Action` and found the path on the
`ReferencedTypes` tab:

    System.Action
        Microsoft.Maui.Platform.ImageButtonExtensions.[]c__DisplayClass4_0
            Google.Android.Material.ImageView.ShapeableImageView
            Microsoft.Maui.Controls.ImageButton

Which led me to the code:

    public static async void UpdatePadding(this ShapeableImageView platformButton, IImageButton imageButton)
    {
        platformButton.SetContentPadding(imageButton);
        platformButton.Post(() =>
        {
            platformButton.SetContentPadding(imageButton);
        });
        platformButton.SetContentPadding(imageButton);
    }

?!? Why is this code calling `SetContentPadding` three times?

Reviewing the commit history:

* a270ebd
* 1bbe79d
* material-components/material-components-android#2063

I could comment out the code and the leak is solved, but I found I could
also change the code to use `await Task.Yield()` for the same result.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment