LayerDrawable: can this be done more efficiently?

837 Views Asked by At

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

1

There are 1 best solutions below

0
On

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.