1

I have an ImageView, full width, and width:height = 3:1, let's call it 1200 x 400. I want to show two Drawables in the ImageView, both of which are only known at runtime. One of the drawables should be placed into the ImageView according to "center-crop" (assuming the drawable has width:height > 3, that means making the width fit exactly and cropping top and bottom), the other one should be centered and scaled by a custom factor.

I have some code that does this, but it seems unnecessarily complicated to me; I only could get it to work as wanted when I create a new Bitmap from the LayerDrawable, then a BitmapDrawable from that, and set the desired bounds again on the BitmapDrawable, although I had already set the bounds on the LayerDrawable - but those bounds are just ignored if I don't perform the extra step. I'd much prefer to generate the LayerDrawable in such a way that I can use it as it is in .setImageDrawable(), but I don't know how. What Android does if I try does not make any sense to me. What am I doing wrong?

This is how I make the LayerDrawable

double divide(int k, int n) {
    return ((double)k)/n;
}
int verticalPaddingForHorizontalFit(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight){
    // you want to draw a drawable into a view, with an exact match in the width. Then either [1] or [2]
    // [1] the view is taller than the drawable (i.e., height/width bigger for the view)
    //     --> method result is positive,
    //     and gives the amount of padding top and bottom
    // [2] the drawable is taller
    //     --> method result is negative,
    //         and gives (minus) the amount that needs to be clipped from top and bottom
    // such that the drawable is vertically centered
    double viewAspect     = divide(viewHeight,     viewWidth    );
    double drawableAspect = divide(drawableHeight, drawableWidth);
    return (int)Math.round(0.5 * viewWidth * (viewAspect - drawableAspect));
}
int[] paddingWhenCenteredAt(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight, double drawableScale, int centerX, int centerY){
    // scale the drawable with drawableScale, and put it into the view
    // such that the center of the drawable has coordinates (centerX, centerY)
    // return the padding needed as array of left, top, right, bottom, in that order
    // negative values indicating clipping instead of padding
    double w = drawableScale * drawableWidth;
    double h = drawableScale * drawableHeight;
    double left = centerX - 0.5*w;
    double right = viewWidth - (centerX + 0.5*w);
    double top = centerY - 0.5*h;
    double bottom = viewHeight - (centerY + 0.5*h);
    return new int[]{(int)Math.round(left), (int)Math.round(top), (int)Math.round(right), (int)Math.round(bottom)};
}
LayerDrawable makeLayerDrawable(Resources r, int outputWidth, int outputHeight, Bitmap bm1, Bitmap bm2){
    Drawable[] layers = new Drawable[2];
    BitmapDrawable bmd1 = new BitmapDrawable(r, bm1);
    int width1  = bmd1.getIntrinsicWidth();
    int height1 = bmd1.getIntrinsicHeight();
    layers[0]   = bmd1;
    BitmapDrawable bmd2 = new BitmapDrawable(r, bm2);
    int width2  = bmd2.getIntrinsicWidth();
    int height2 = bmd2.getIntrinsicHeight();
    layers[1]   = bmd2;
    LayerDrawable result = new LayerDrawable(layers);
    int vPad = verticalPaddingForHorizontalFit(outputWidth, outputHeight, width1, height1);
    result.setLayerInset(0, 0, vPad, 0, vPad);
    int[] ltrb = paddingWhenCenteredAt(outputWidth, outputHeight, width2, height2, 0.5, outputWidth/2, outputHeight/2);
    result.setLayerInset(1, ltrb[0], ltrb[1], ltrb[2], ltrb[3]);
    result.setBounds(0, 0, outputWidth, outputHeight);
    return result;
}

(I tested with Bitmap bm1 2400 x 1200 pixels and Bitmap bm2 800 x 300 pixels.)

Now, if I just use that LayerDrawable, like so

    myImageView.setImageDrawable(layd);

the ImageView will not have the desired size (height changes.) If I set the layout again with LayoutParameters, I can prevent that, but then, the drawables are not correctly shown. If instead, I do this

    LayerDrawable layd = makeLayerDrawable(r, outputWidth, outputHeight, bm1, bm2);
    Bitmap b = Bitmap.createBitmap(outputWidth, outputHeight, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(b);
    layd.draw(canvas);
    BitmapDrawable result = new BitmapDrawable(getResources(), b);
    result.setBounds(0, 0, outputWidth, outputHeight);
    myImageView.setImageDrawable(result);

it works.

Here is the complete code on github

3
  • ImageView calls setBounds() on the image drawable as part of layout -- calling that manually to attempt to set the bounds won't have any effect. You should consider setting the gravity on the BitmapDrawable layers instead.
    – alanv
    Commented Jan 28, 2015 at 1:00
  • @alanv. 1) I think gravity is not flexible enough for my purposes. One of the drawables is neither in the center nor in any corner. 2) I know what I want the pixel dimensions of the view to be (screenWidth x screenWidth/3) - is there no way to tell the ImageView to cut out trying to be smarter than me, and not calling setBounds()? Commented Jan 28, 2015 at 4:45
  • You would need to override ImageView's default measurement and layout. The answer below that suggests writing your own Drawable is probably the best option, but you might be better off sub-classing LayerDrawable and override the onBoundsChange() method to do your own setBounds() calls on the individual layers.
    – alanv
    Commented Jan 28, 2015 at 17:38

1 Answer 1

0

I would consider to write your own Drawable class which is not that hard as it seems. Simply extend from Drawable and override draw() where you do pretty the same calculation as you did in your code snipped just to draw to bitmaps layered (stacked) with the required aspect ratio. Canvas.drawBitmap() seems to work for your problem.

Not the answer you're looking for? Browse other questions tagged or ask your own question.