简体   繁体   中英

How to set the size of vector drawable inside of a button in Android?

The Android Studio Vector Assets tools convert vector drawable to PNG-s for devices pre-Lollipop but I get really bad quality PNG-s as you can see here:

将矢量转换为 PNG

What's more is that the button's background solid color is supposed to be this light green that you see on the left but the drawable overwrite it:

<item android:state_checked="true"
    android:drawable="@drawable/show">
    <shape android:shape="rectangle">
        <corners android:bottomRightRadius="8dp"/>
        <solid android:color="@color/waveComponentGreen"/>
    </shape>
</item>

<item android:state_checked="false"
    android:drawable="@drawable/hide">
    <shape android:shape="rectangle">
        <corners android:bottomRightRadius="8dp"/>
        <solid android:color="@color/waveComponentGreen"/>
    </shape>
</item>

The xml for the drawable is (the default from the material icons):

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
<path
    android:fillColor="#FF000000"
    android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10,5.75l6,6 -6,6z"/>

I wanted also to make the icon appear a bit smaller by tweaking the values and I noticed increasing the viewport dimensions decreases the icon but I'm not sure I understand why.

So: How do I make the icon and the generated PNG appear smaller, less blurry and with the background colour set in the resource file? Thank you.

EDIT : I managed to get the solid colour background with the icon by combining them in a separate xml file with layer-lists:

<layer-list
xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
    <shape android:shape="rectangle">
        <corners android:bottomRightRadius="10dp"/>
        <solid android:color="@color/waveComponentGreen"/>
    </shape>
</item>
<item android:drawable="@drawable/show"
    android:top="10dp"
    android:bottom="10dp"
    android:left="10dp"
    android:right="10dp"
    />

The result is:

一些调整的结果

I managed to reduce the blurring by increasing the width and height of the vector drawable. However without the android:top|bottom|left|right tags, the drawable is stretched across the whole area of the button. The second button doesn't need to have a background solid color so I'm not using the layer-list tags => no way to set a top|bottom|left|right margin for the drawable.
If I reduce the button size what I'm doing is reducing the clickable area of the button.

My updated question is how to set the size of the vector drawable inside a button/toggle button/radio button without reducing the size of the button itself?

UPDATE
I couldn't find a way to resize the vector drawable on pre-API 21 devices. So instead I made the buttons themselves smaller and increased the touch area of each button.

The correct approach to scale any drawable would be to use vectorDrawable.setBounds(left,top,right,bottom) , but unfortunately that does not work for vector drawables (why Google ?).

So as a workaround I load my vector drawables , convert them to bitmap drawable and that will allow us to use the setBounds method on the bitmap drawable. Note that you are scaling bitmaps here , so you can lose some sharpness of the image. I mainly use those methods when I need to use my drawable as a compound drawable of a text view or a button for example.

I ended up writing a helper class that will load a vector drawable set a tint to it and return a bitmap drawable that you can actually scale and tint as you wish. I've tested it for API levels 19 up to 23 , and it works.

Don't forget to use vectorDrawables.useSupportLibrary = true in your build.gradle .

public class VectorDrawableUtils {

/**
 * Gets a Bitmap from provided Vector Drawable image
 *
 * @param vd VectorDrawable
 * @return Bitmap
 */
public static Optional<Bitmap> createBitmapFromVectorDrawable(final @NonNull Drawable vd) {
    try {
        Bitmap bitmap;
        bitmap = Bitmap.createBitmap(vd.getIntrinsicWidth(), vd.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        vd.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        vd.draw(canvas);
        return Optional.of(bitmap);
    } catch (OutOfMemoryError e) {
        Injector.getDependency(getContext(), IEventTracker.class).logHandledException(e);
        return Optional.empty();
    }
}

/**
 * Loads vector drawable and apply tint color on it.
 */
public static Drawable loadVectorDrawableWithTintColor(final @DrawableRes int vdRes,
                                                       final @ColorRes int clrRes,final Context context) {
    Drawable drawable = ContextCompat.getDrawable(context, vdRes);
    DrawableCompat.setTint(drawable, getContext().getResources().getColor(clrRes));
    return drawable;
}

/**
 * Converts given vector drawable to Bitmap drawable
 */
public static BitmapDrawable convertVectorDrawableToBitmapDrawable(final @NonNull Drawable vd) {
    //it is safe to create empty bitmap drawable from null source
    return new BitmapDrawable(createBitmapFromVectorDrawable(vd).get());
}

/**
 * Loads vector drawable , aplys tint on it and returns a wrapped bitmap drawable.
 * Bitmap drawable can be resized using setBounds method (unlike the VectorDrawable)
 * @param context Requires view context !
 */
public static Drawable loadVectorDrawableWithTint(
        final @DrawableRes int vectorDrawableRes, final @ColorRes int colorRes,final Context context) {
    Drawable vd = VectorDrawableUtils.loadVectorDrawableWithTintColor(vectorDrawableRes,
            colorRes, context);
    final BitmapDrawable bitmapDrawable = VectorDrawableUtils.convertVectorDrawableToBitmapDrawable(vd);
    ColorStateList tint = ContextCompat.getColorStateList(context,colorRes);
    final Drawable wrappedDrawable = DrawableCompat.wrap(bitmapDrawable);
    DrawableCompat.setTintList(wrappedDrawable,tint);
    return wrappedDrawable;
    }
}

Now I would use this helper class like this :

    Drawable bd = VectorDrawableUtils.loadVectorDrawableWithTint(
                R.drawable.ic_dropdown, R.color.black,getContext());
        bd.setBounds(0, 0, textView.getMeasuredHeight(), textView.getMeasuredHeight());
        textView.setCompoundDrawablesWithIntrinsicBounds(null, null, bd, null);

It is important to use a Context of a View or Activity, not the Application context! Hope it will solve your problem, or help someone else. And if someone has a better and cleaner solution, I am interested to know as well.

MyTextView class:

public class MyTextView extends AppCompatTextView {

public MyTextView(Context context) {
    super(context);
}

public MyTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initAttrs(context, attrs);
}

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initAttrs(context, attrs);
}

void initAttrs(Context context, AttributeSet attrs) {
    if (attrs != null) {
        TypedArray attributeArray = context.obtainStyledAttributes(
                attrs,
                R.styleable.MyTextView);

        int defaultWidthHeight = 0;
        int widthHeight = 0;
        Drawable drawableLeft = null;
        Drawable drawableStart = null;
        Drawable drawableRight = null;
        Drawable drawableEnd = null;
        Drawable drawableBottom = null;
        Drawable drawableTop = null;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            drawableLeft = attributeArray.getDrawable(R.styleable.MyTextView_drawableLeftCompatTextView);
            drawableStart = attributeArray.getDrawable(R.styleable.MyTextView_drawableStartCompatTextView);
            drawableRight = attributeArray.getDrawable(R.styleable.MyTextView_drawableRightCompatTextView);
            drawableEnd = attributeArray.getDrawable(R.styleable.MyTextView_drawableEndCompatTextView);
            drawableBottom = attributeArray.getDrawable(R.styleable.MyTextView_drawableBottomCompatTextView);
            drawableTop = attributeArray.getDrawable(R.styleable.MyTextView_drawableTopCompatTextView);
        } else {
            final int drawableLeftId = attributeArray.getResourceId(R.styleable.MyTextView_drawableLeftCompatTextView, -1);
            final int drawableStartId = attributeArray.getResourceId(R.styleable.MyTextView_drawableStartCompatTextView, -1);
            final int drawableRightId = attributeArray.getResourceId(R.styleable.MyTextView_drawableRightCompatTextView, -1);
            final int drawableEndId = attributeArray.getResourceId(R.styleable.MyTextView_drawableEndCompatTextView, -1);
            final int drawableBottomId = attributeArray.getResourceId(R.styleable.MyTextView_drawableBottomCompatTextView, -1);
            final int drawableTopId = attributeArray.getResourceId(R.styleable.MyTextView_drawableTopCompatTextView, -1);

            if (drawableLeftId != -1)
                drawableLeft = AppCompatResources.getDrawable(context, drawableLeftId);
            if(drawableStartId != -1)
                drawableStart = AppCompatResources.getDrawable(context, drawableStartId);
            if (drawableRightId != -1)
                drawableRight = AppCompatResources.getDrawable(context, drawableRightId);
            if(drawableEndId != -1)
                drawableEnd = AppCompatResources.getDrawable(context, drawableEndId);
            if (drawableBottomId != -1)
                drawableBottom = AppCompatResources.getDrawable(context, drawableBottomId);
            if (drawableTopId != -1)
                drawableTop = AppCompatResources.getDrawable(context, drawableTopId);
        }

        if(!attributeArray.hasValue(R.styleable.MyTextView_drawableWidthHeightCompatTextView)) {
            if (attributeArray.hasValue(R.styleable.MyTextView_drawableLeftCompatTextView)) {
                defaultWidthHeight = drawableLeft.getIntrinsicWidth();
            } else if (attributeArray.hasValue(R.styleable.MyTextView_drawableStartCompatTextView)) {
                defaultWidthHeight = drawableStart.getIntrinsicWidth();
            } else if (attributeArray.hasValue(R.styleable.MyTextView_drawableRightCompatTextView)) {
                defaultWidthHeight = drawableRight.getIntrinsicWidth();
            } else if (attributeArray.hasValue(R.styleable.MyTextView_drawableEndCompatTextView)) {
                defaultWidthHeight = drawableEnd.getIntrinsicWidth();
            } else if (attributeArray.hasValue(R.styleable.MyTextView_drawableBottomCompatTextView)) {
                defaultWidthHeight = drawableBottom.getIntrinsicWidth();
            } else if (attributeArray.hasValue(R.styleable.MyTextView_drawableTopCompatTextView)) {
                defaultWidthHeight = drawableTop.getIntrinsicWidth();
            }

            widthHeight = attributeArray.getInt(R.styleable.MyTextView_drawableWidthHeightCompatTextView, defaultWidthHeight);
        } else
            widthHeight = attributeArray.getInt(R.styleable.MyTextView_drawableWidthHeightCompatTextView, defaultWidthHeight);

        if(attributeArray.hasValue(R.styleable.MyTextView_drawableColorCompatTextView)){
            ColorStateList tintColor = attributeArray.getColorStateList(R.styleable.MyTextView_drawableColorCompatTextView);
            if (attributeArray.hasValue(R.styleable.MyTextView_drawableLeftCompatTextView)) {
                //drawableLeft.setColorFilter(new PorterDuffColorFilter(tintColor.getDefaultColor(), PorterDuff.Mode.MULTIPLY));
                DrawableCompat.setTintList(drawableLeft, tintColor);
            } else if (attributeArray.hasValue(R.styleable.MyTextView_drawableStartCompatTextView)) {
                //drawableStart.setColorFilter(new PorterDuffColorFilter(tintColor.getDefaultColor(), PorterDuff.Mode.MULTIPLY));
                DrawableCompat.setTintList(drawableStart, tintColor);
            } else if (attributeArray.hasValue(R.styleable.MyTextView_drawableRightCompatTextView)) {
                //drawableRight.setColorFilter(new PorterDuffColorFilter(tintColor.getDefaultColor(), PorterDuff.Mode.MULTIPLY));
                DrawableCompat.setTintList(drawableRight, tintColor);
            } else if (attributeArray.hasValue(R.styleable.MyTextView_drawableEndCompatTextView)) {
                //drawableEnd.setColorFilter(new PorterDuffColorFilter(tintColor.getDefaultColor(), PorterDuff.Mode.MULTIPLY));
                DrawableCompat.setTintList(drawableEnd, tintColor);
            } else if (attributeArray.hasValue(R.styleable.MyTextView_drawableBottomCompatTextView)) {
                //drawableBottom.setColorFilter(new PorterDuffColorFilter(tintColor.getDefaultColor(), PorterDuff.Mode.MULTIPLY));
                DrawableCompat.setTintList(drawableBottom, tintColor);
            } else if (attributeArray.hasValue(R.styleable.MyTextView_drawableTopCompatTextView)) {
                //drawableTop.setColorFilter(new PorterDuffColorFilter(tintColor.getDefaultColor(), PorterDuff.Mode.MULTIPLY));
                DrawableCompat.setTintList(drawableTop, tintColor);
            }
        }

        WrappedDrawable drawableLeftWrapped = new WrappedDrawable(drawableLeft);
        drawableLeftWrapped.setBounds(0, 0, widthHeight, widthHeight);
        WrappedDrawable drawableStartWrapped = new WrappedDrawable(drawableStart);
        drawableStartWrapped.setBounds(0, 0, widthHeight, widthHeight);
        WrappedDrawable drawableRightWrapped = new WrappedDrawable(drawableRight);
        drawableRightWrapped.setBounds(0, 0, widthHeight, widthHeight);
        WrappedDrawable drawableEndWrapped = new WrappedDrawable(drawableEnd);
        drawableEndWrapped.setBounds(0, 0, widthHeight, widthHeight);
        WrappedDrawable drawableBottomWrapped = new WrappedDrawable(drawableBottom);
        drawableBottomWrapped.setBounds(0, 0, widthHeight, widthHeight);
        WrappedDrawable drawableTopWrapped = new WrappedDrawable(drawableTop);
        drawableTopWrapped.setBounds(0, 0, widthHeight, widthHeight);

        setCompoundDrawablesWithIntrinsicBounds(drawableLeftWrapped, drawableTopWrapped, drawableRightWrapped, drawableBottomWrapped);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
            setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStartWrapped, drawableTopWrapped, drawableEndWrapped, drawableBottomWrapped);

        attributeArray.recycle();
    }
}

class WrappedDrawable extends Drawable {

    private final Drawable _drawable;
    protected Drawable getDrawable() {
        return _drawable;
    }

    public WrappedDrawable(Drawable drawable) {
        super();
        _drawable = drawable;
    }

    @Override
    public void setBounds(int left, int top, int right, int bottom) {
        //update bounds to get correctly
        super.setBounds(left, top, right, bottom);
        Drawable drawable = getDrawable();
        if (drawable != null) {
            drawable.setBounds(left, top, right, bottom);
        }
    }

    @Override
    public void setAlpha(int alpha) {
        Drawable drawable = getDrawable();
        if (drawable != null) {
            drawable.setAlpha(alpha);
        }
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        Drawable drawable = getDrawable();
        if (drawable != null) {
            drawable.setColorFilter(colorFilter);
        }
    }

    @Override
    public int getOpacity() {
        Drawable drawable = getDrawable();
        return drawable != null
                ? drawable.getOpacity()
                : PixelFormat.UNKNOWN;
    }

    @Override
    public void draw(Canvas canvas) {
        Drawable drawable = getDrawable();
        if (drawable != null) {
            drawable.draw(canvas);
        }
    }

    @Override
    public int getIntrinsicWidth() {
        Drawable drawable = getDrawable();
        return drawable != null
                ? drawable.getBounds().width()
                : 0;
    }

    @Override
    public int getIntrinsicHeight() {
        Drawable drawable = getDrawable();
        return drawable != null ?
                drawable.getBounds().height()
                : 0;
    }
}
}

attrs.xml:

<declare-styleable name="MyTextView">
    <attr name="drawableColorCompatTextView" format="reference|color"/>
    <attr name="drawableWidthHeightCompatTextView" format="integer"/>
    <attr name="drawableLeftCompatTextView" format="reference"/>
    <attr name="drawableStartCompatTextView" format="reference"/>
    <attr name="drawableRightCompatTextView" format="reference"/>
    <attr name="drawableEndCompatTextView" format="reference"/>
    <attr name="drawableTopCompatTextView" format="reference"/>
    <attr name="drawableBottomCompatTextView" format="reference"/>
</declare-styleable>

Usage:

<com.packagename.MyTextView
    android:id="@+id/txtUserName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:drawableLeftCompatTextView="@drawable/ic_username"
    app:drawableStartCompatTextView="@drawable/ic_username"
    app:drawableWidthHeightCompatTextView="48"
    app:drawableColorCompatTextView="@color/blue" />

Note: The only issue here is an unmodified vector( drawableWidthHeightCompatTextView didn't use), which vector's width and height are 24,
Is not equal in size on the device, with resized vector(vector's width and height are 12 and drawableWidthHeightCompatTextView="24" ).

look at this I found, it works in Vector and regular Drawables! I think it is the shortest solution I saw.

The idea is to create a custom drawable with fixed intrinsic size and pass the drawing job on to the original drawable.

final Drawable d = container.getContext().getDrawable(R.drawable.your_drawable);
    Drawable icon = new ColorDrawable(){
        Drawable iconOrig = d;

        @Override
        public void setBounds(int left, int top, int right, int bottom){
            super.setBounds(left, top, right, bottom);//This is needed so that getBounds on this class would work correctly.
            iconOrig.setBounds(left, top, right, bottom);
        }

        @Override
        public void draw(Canvas canvas){
            iconOrig.draw(canvas);
        }

        @Override
        public int getIntrinsicWidth(){
            return  27;//the width you want
        }

        @Override
        public int getIntrinsicHeight(){
            return  19;// the height you want
        }
    };

I used a FrameLayout to put an ImageButton and an ImageView together and to be able to resize the ImageView :

<FrameLayout
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="60dp"
  android:layout_height="60dp"
>
  <ImageButton
    android:layout_width="match_parent"
    android:layout_height="match_parent"
  />
  <ImageView
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:layout_gravity="center"
    app:srcCompat="@drawable/baseline_arrow_right_24"
  />
</FrameLayout>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM