CompoundDrawable size
Asked Answered
L

6

30

Android-Lint gives me this hint in some cases:

This tag and its children can be replaced by one and a compound drawable

I could do it on some places, but on other places where scaling the image is important I am not able to do it. Is there any option that i can set the size for a compound drawable?

Linzy answered 6/2, 2012 at 22:6 Comment(1)
You'll need to post an example of what you're talking about? What do you mean by "compound drawable"?Gabriello
O
31

I finally released a library for it:

https://github.com/a-tolstykh/textview-rich-drawable

Use this library to set size of compound drawable.

Original answer

You can add custom attribute to your view to work with size of drawable. Of course, this solution requires additional code, but only once! After you can solve the problem using xml only (only once change the control and use it in all project).

See my example:

TextViewDrawableSize.java

public class TextViewDrawableSize extends TextView {

    private int mDrawableWidth;
    private int mDrawableHeight;

    public TextViewDrawableSize(Context context) {
        super(context);
        init(context, null, 0, 0);
    }

    public TextViewDrawableSize(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0, 0);
    }

    public TextViewDrawableSize(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr, 0);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public TextViewDrawableSize(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context, attrs, defStyleAttr, defStyleRes);
    }

    private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextViewDrawableSize, defStyleAttr, defStyleRes);

        try {
            mDrawableWidth = array.getDimensionPixelSize(R.styleable.TextViewDrawableSize_compoundDrawableWidth, -1);
            mDrawableHeight = array.getDimensionPixelSize(R.styleable.TextViewDrawableSize_compoundDrawableHeight, -1);
        } finally {
            array.recycle();
        }

        if (mDrawableWidth > 0 || mDrawableHeight > 0) {
            initCompoundDrawableSize();
        }
    }

    private void initCompoundDrawableSize() {
        Drawable[] drawables = getCompoundDrawables();
        for (Drawable drawable : drawables) {
            if (drawable == null) {
                continue;
            }

            Rect realBounds = drawable.getBounds();
            float scaleFactor = realBounds.height() / (float) realBounds.width();

            float drawableWidth = realBounds.width();
            float drawableHeight = realBounds.height();

            if (mDrawableWidth > 0) {
                // save scale factor of image
                if (drawableWidth > mDrawableWidth) {
                    drawableWidth = mDrawableWidth;
                    drawableHeight = drawableWidth * scaleFactor;
                }
            }
            if (mDrawableHeight > 0) {
                // save scale factor of image

                if (drawableHeight > mDrawableHeight) {
                    drawableHeight = mDrawableHeight;
                    drawableWidth = drawableHeight / scaleFactor;
                }
            }

            realBounds.right = realBounds.left + Math.round(drawableWidth);
            realBounds.bottom = realBounds.top + Math.round(drawableHeight);

            drawable.setBounds(realBounds);
        }
        setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3]);
    }
}

attrs.xml

<resources>
    <declare-styleable name="TextViewDrawableSize">
        <attr name="compoundDrawableWidth" format="dimension"/>
        <attr name="compoundDrawableHeight" format="dimension"/>
    </declare-styleable>
</resources>

And usage

    <com.alt.view.TextViewDrawableSize
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:drawableLeft="@drawable/logo"
        android:drawableStart="@drawable/logo"
        android:drawablePadding="@dimen/space_micro"
        app:compoundDrawableHeight="@dimen/small_logo_height"
        app:compoundDrawableWidth="@dimen/small_logo_width"
        />
Obscurant answered 10/8, 2015 at 10:3 Comment(8)
Awesome! I've made this based on your code gist.github.com/hrules6872/578b52fe90c30c7445a2 thank you :)Broody
@hrules6872 I am using your code but apply it on EditText, but I am having two problems. 1. there is a small vertical bar (|) and start and end of "EditText". and 2. The hint (placeholder) color uses android:textColor. Any idea why? (It is only occurs on API 19 - Android KitKat)Tidwell
@sam the vertical bars may be as a part of background and issue with hint color sounds really strange. Don't you mind to add this functionality to the library and we can figure out the problem together?Obscurant
Unfortunately, the posted code does not support dynamic update of the drawable.Junie
@azizbekian, could you report an issue on gitHub? Or even create a pull-request)Obscurant
Google half a... this feature, and bothers developers with warnings, but it's a poorly implemented feature. I mean, look at how much more code you have to write to get it to work. I'd just go with the old easy 2 views.Mysterious
@TatiOverflow, unfortunately you are right, this feature is poorly implemented. I would say it's always a trade off between performance and code readability. For a small project it wouldn't make sense to use 2 views, but for a RecyclerView with many items with complex layout it would be beneficial to use this approach to simplify the layout. Thanks for sharing your point on this.Obscurant
@Obscurant I don't get why you put both drawableStart and drawableLeft. Your code is awesome, but drawableStart does nothing for me... And I get the warnings "use start instead of left to better support right to left..." except this little point (not your fault) Good job man you saved me.Crowboot
S
22

If you scale the images in code as Drawable objects, you can then set them in code using the setCompoundDrawables() method of TextView. This will require having called setBounds() on the Drawables.

As far as I know there is no way to set the size in XML.

Shed answered 6/2, 2012 at 22:20 Comment(2)
thanks for the answer even if I do not like the fact that it is not possible to do it in XML and have to choose between 2 suboptimal solutions ( warning about not using compound drawable v.s. intermixing layout and code )Linzy
@Linzy You can do it with simple custom view, see my answer - https://mcmap.net/q/120656/-compounddrawable-sizeObscurant
A
6

There is a solution but its not 100% satisfying since you cannot control the size directly only the relative padding with inset:

background.xml

<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
       android:insetBottom="4dp"
       android:insetTop="4dp"
       android:insetLeft="4dp"
       android:insetRight="4dp">
    <bitmap android:src="@drawable/your_icon"/>
</inset>

layout.xml

<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Your text"
        android:drawableLeft="@drawable/background"
        android:drawablePadding="8dp"
        />

Another solution would ScaleDrawables, but I guess this wont work great over different pixel densities.

Affray answered 3/9, 2013 at 12:9 Comment(1)
Inset works very well in KitKat, but doesn't work in Marsmallow. Any idea why?Tidwell
C
3

Programmatic solution:

You can use drawable.setBounds but it requires pixels rather than dp as parameters.

how to convert: Converting pixels to dp

code:

        Drawable drawable = getDrawable(R.drawable.ic_your_drawable);
        int size = dp2px(this, dpSize);
        if (drawable != null) {
            drawable.setBounds(0, 0, size, size);
        }

        textView.setCompoundDrawables(drawable, drawable, drawable, drawable);
Catabasis answered 16/5, 2020 at 9:32 Comment(0)
T
1

I know this answer is a little late but I would recommend using the quickfix: "Add ignore 'UseCompoundDrawables' to element"

This will result in something like:

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    tools:ignore="UseCompoundDrawables" >  <!-- This will remove the warning -->

    <ImageView
        android:layout_width="@dimen/icon_width"
        android:layout_height="@dimen/icon_height"
        android:contentDescription="@string/item1"
        android:src="@drawable/icon1" />

    <TextView
        android:text="@string/item1"
        android:textSize="@dimen/label_font_size" />
</LinearLayout>

Use with caution and lets hope that the API or tools gets "fixed" soon.

Tubercular answered 23/10, 2012 at 16:50 Comment(4)
To add to this it's worth mentioning the xml namespace for this is: xmlns:tools="http://schemas.android.com/tools"Okeefe
But this is against efficiency! Android warns you for something! Inflating a Linear Layout and a ImageView and a TextView is completely avoidable if you just use a CompoundDrawable as suggestedFrisby
@joaquin The point is that it's not possible to get a compounddrawable to look exactly like what it would in a linear layout with a specified height/width or scaling etc right now. There are some proposed solutions above but realistically these add unnecessary complexity/indirection and are less efficient than using a linear layout.Intuitive
Exactly. Here is google harassing you about using image views, but then leave you hanging when attempting their solution. If you really want people to use compound drawable, why don't you provide an easy way to provide the size?Mysterious
C
1

@Oleksandr answer is the best one especially if your are going to change the Drawable programmatically, but you should add :

    @Override
public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end, int bottom) {
    super.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
    initCompoundDrawableSize();

}

so it will Re-Calculate whenever Coumpounds is changed.

Convolve answered 19/9, 2018 at 8:57 Comment(1)
Ah ok - that is strange that you can answer but not comment. Did not know that. Would upvote your post now so you get closer to the 50 - but cannot as you need to edit the post first ..Linzy

© 2022 - 2024 — McMap. All rights reserved.