Search code examples
androidspannablestringandroid-snackbarsnackbarspannablestringbuilder

Why does adding an ImageSpan to a Snackbar's action text work on Android devices SDK level 26 but not on SDK level 25?


I want to show a Snackbar and use an image instead of text for the action.

I use the following code:

    val imageSpan = ImageSpan(this, R.drawable.star)
    val builder = SpannableStringBuilder(" ")
    builder.setSpan(
        imageSpan,
        0,
        1,
        SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE
    )
    Snackbar.make(findViewById(R.id.container), "Hello Snackbar", Snackbar.LENGTH_INDEFINITE)
        .setAction(builder) {}.show()

drawable_star being a vector graphic asset, but the same happens with a png.

On an Android device lvl 26 and above this yields:

pic1

as expected, whereas on device lvl 25 the image is not visible:

pic2

Does someone know the reason for this and if there a workaround?

PS: You can check out my test project here: https://github.com/fmweigl/SpannableTest


Solution

  • That's due to the textAllCaps bug on versions prior to Oreo. That Button's default style will have that attribute set to true, which simply causes the Button's text to be converted to all uppercase. That conversion is done with the platform AllCapsTransformationMethod class, which, on Nougat 7.1 and below, would treat everything as flat Strings, essentially stripping any formatting spans you've set.

    The fix is to turn that attribute off, and handle any uppercase conversion you might need yourself, in code. Snackbar offers the snackbarButtonStyle attribute as a means to style the action Button, and we can create a simple style to modify that value. For example, from your styles.xml:

    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    
        <item name="snackbarButtonStyle">@style/NoCapsButton</item>>
    </style>
    
    <style name="NoCapsButton" parent="Widget.AppCompat.Button">
        <item name="textAllCaps">false</item>
    </style>
    

    (If you're using a Material Components theme, the parent for NoCapsButton should instead be Widget.MaterialComponents.Button.TextButton.Snackbar.)


    In this specific case, that's all you need to do, since there's no text to convert.