42

I am setting a drawable for a progress dialog (pbarDialog) but my issue is I want to resize the drawable each time but can't figure out how.

Here is some code:

Handler progressHandler = new Handler() {

    public void handleMessage(Message msg) {
        switch (msg.what) {
            // some more code
            case UPDATE_PBAR:
                pbarDialog.setIcon(mAppIcon);
                pbarDialog.setMessage(mPbarMsg);
                pbarDialog.incrementProgressBy(mIncrement+1);
                break;
        }
    }
};

pbarDialog.show();

Thread myThread = new Thread(new Runnable() {

    public void run() {
        // some code
        for (int i = 0; i < mApps.size(); i++) {
            mAppIcon = mAdapter.getIcons().get(mApps.get(i).getPackageName());
            // need to resize drawable here
            progressHandler.sendEmptyMessage(UPDATE_PBAR);
        }
        handler.sendEmptyMessage(DISMISS_PBAR);
    }

});

myThread.start();
tar
  • 1,538
  • 1
  • 20
  • 33
mDude26
  • 421
  • 1
  • 4
  • 3

7 Answers7

82

The following worked for me:

private Drawable resize(Drawable image) {
    Bitmap b = ((BitmapDrawable)image).getBitmap();
    Bitmap bitmapResized = Bitmap.createScaledBitmap(b, 50, 50, false);
    return new BitmapDrawable(getResources(), bitmapResized);
}
matthijs2704
  • 110
  • 1
  • 13
Saad Farooq
  • 13,172
  • 10
  • 68
  • 94
  • 1
    I got this error at fist line of code : java.lang.ClassCastException: android.graphics.drawable.PictureDrawable cannot be cast to android.graphics.drawable.BitmapDrawable – Misagh Aghakhani Apr 22 '14 at 07:16
  • 6
    use a Drawable as argument and cast it to BitmapDrawable !? if your are sure that it's always a BitmapDrawable so change argument type to BitmapDrawable too, if not maybe it's nice to check drawable type before cast. – Farshad Jun 20 '17 at 13:17
24

Here's where I ended up, thanks in part to Saad's answer:

public Drawable scaleImage (Drawable image, float scaleFactor) {

    if ((image == null) || !(image instanceof BitmapDrawable)) {
        return image;
    }

    Bitmap b = ((BitmapDrawable)image).getBitmap();

    int sizeX = Math.round(image.getIntrinsicWidth() * scaleFactor);
    int sizeY = Math.round(image.getIntrinsicHeight() * scaleFactor);

    Bitmap bitmapResized = Bitmap.createScaledBitmap(b, sizeX, sizeY, false);

    image = new BitmapDrawable(getResources(), bitmapResized);

    return image;

}
William T. Mallard
  • 1,562
  • 2
  • 25
  • 33
  • perfect, this is the path i took. – Tony Jul 28 '14 at 01:16
  • Nice coding. Exactly what I needed. Thank you, brother :) – Martin Pfeffer May 25 '15 at 03:16
  • Be careful with this : the former drawable "image" contains a bitmap that has not been recycled. Calling "image = scaleImage(image, 2)" would cause a temporary leak.The caller of scaleImage thus has the responsibility to recycle both the original image and the scaled image... This may not be a problem in small apps but it is critical in memory-intensive apps. – JM Lord Jun 25 '15 at 18:31
  • @JMLord Good point, more on that here: https://developer.android.com/training/displaying-bitmaps/manage-memory.html – William T. Mallard Jun 25 '15 at 19:06
  • Any solutions or alternatives to this then that will avoid the memory leak – Lion789 Mar 22 '16 at 05:27
  • Great solution. Tested in Xamarin.Forms as well. – ethane May 30 '17 at 11:43
9

For the resizing, this is nice and short (the code above wasn't working for me), found here:

  ImageView iv = (ImageView) findViewById(R.id.imageView);
  Bitmap bMap = BitmapFactory.decodeResource(getResources(), R.drawable.picture);
  Bitmap bMapScaled = Bitmap.createScaledBitmap(bMap, newWidth, newHeight, true);
  iv.setImageBitmap(bMapScaled);
craned
  • 2,991
  • 2
  • 34
  • 38
7

Here is a combination of the above answers as a Kotlin extension

fun Context.scaledDrawableResources(@DrawableRes id: Int, @DimenRes width: Int, @DimenRes height: Int): Drawable {
    val w = resources.getDimension(width).toInt()
    val h = resources.getDimension(height).toInt()
    return scaledDrawable(id, w, h)
}

fun Context.scaledDrawable(@DrawableRes id: Int, width: Int, height: Int): Drawable {
    val bmp = BitmapFactory.decodeResource(resources, id)
    val bmpScaled = Bitmap.createScaledBitmap(bmp, width, height, false)
    return BitmapDrawable(resources, bmpScaled)
}

Usage:

val scaled = context.scaledDrawableResources(R.drawable.ic_whatever, R.dimen.width, R.dimen.height)
imageView.setImageDrawable(scaled)

or

val scaled = context.scaledDrawable(R.drawable.ic_whatever, 100, 50)
imageView.setImageDrawable(scaled)
Chad Bingham
  • 32,650
  • 19
  • 86
  • 115
1

Maybe my solution covers the question not completely, but I needed something like a "CustomDrawable".

In other words, I want to set a logo in front of a circle shape. So I created a FrameLayout with a background (just a colored circle) and in front of this round shape I show the logo.

To resize the logo I shrink the logo by scaling - here is some code:

iv = new ImageView(mContext);

iv.setScaleX(0.75f); // <- resized by scaling 
iv.setScaleY(0.75f);

// loading the drawable from a getter (replace this with any drawable)
Drawable drawable = ML.loadIcon(mContext, Integer.parseInt(icon));

iv.setImageDrawable(drawable);

// icon get's shown inside a ListView
viewHolder.mIvIcon.addView(iv);

Here is the FrameLayout which shows the icon inside ListView's row:

<FrameLayout
    android:id="@+id/iv_card_icon"
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:src="@drawable/circle"
    android:layout_marginStart="16dp"
    />

See this solution as an option / idea.

halfer
  • 19,824
  • 17
  • 99
  • 186
Martin Pfeffer
  • 12,471
  • 9
  • 59
  • 68
1

The most voted answer wont work if the source Drawable is not instanceof BitmapDrawable which can be the case of using vector, color drawables, etc...

The most appropriate solution could be to draw the Drawable into a Canvas with set bitmap, as following:

@NonNull final Drawable drawable = yourSourceDrawable;

// Define the Canvas and Bitmap the drawable will be drawn against
final Canvas c = new Canvas();
c.setBitmap(bitmap);

// Draw the scaled drawable into the final bitmap
if (yourSourceDrawable!= null) {
    yourSourceDrawable.setBounds(0, 0, newWidth, newHeight);
    yourSourceDrawable.draw(c);
}

BONUS: To calculate the scale to be applied (e.g. when scaling the Drawable to a view):

if (drawable != null && drawable.getIntrinsicWidth() > 0 && drawable.getIntrinsicHeight() > 0) {
    // the intrinsic dimensions can be -1 in some cases such as ColorDrawables which aim to fill 
    // the whole View
    previewWidth = drawable.getIntrinsicWidth();
    previewHeight = drawable.getIntrinsicHeight();
}

final float widthScale = mViewWidth / (float) (previewWidth);
if (widthScale != 1f)
    newWidth = Math.max((int)(widthScale * previewWidth), 1);

final float heightScale = mViewHeight / (float) (previewHeight);
if (heightScale != 1f)
    newHeight = Math.max((int)(heightScale * previewHeight), 1);

NOTE: ALWAYS do this in a worker thread!

BamsBamx
  • 4,139
  • 4
  • 38
  • 63
1

Kotlin way

What it ended up working for me was this simple solution

fun resizeDrawable(width:Int, height:Int): Drawable {
        val drawable = ResourceUtils.getDrawable(R.drawable.ic_info)
        val bitmap = drawable.toBitmap(width, height) //here width and height are in px
        return bitmap.toDrawable(getResources())
    }
Gastón Saillén
  • 12,319
  • 5
  • 67
  • 77