I have an ImageView
, full width, and width:height = 3:1, let's call it 1200 x 400. I want to show two Drawable
s 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
I would consider to write your own
Drawable
class which is not that hard as it seems. Simply extend fromDrawable
and overridedraw()
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.