I faced the same problem recently for setting the background image of an ImageView
. The solution I came up with works with any View
, so it should cover Button
as well.
The issue is that View
always stretches its background to the full extent of View
's width and height, no matter the scaling of the Drawable provided as background. As your report on failing to provide specialized Drawables suggests, this cannot be counteracted by any means of scaling an image prior to setting it as background because View
would simply take the image dimensions, whatever they are, and scale them again independently to fill the View
's area.
My solution takes a different approach based on the fact that if we can provide a background image with same aspect ratio as the View
's, there would be no distortion.
Function scaleKeepingAspect()
below takes an image resource and answers a BitmapDrawable
that contains the original image scaled to a desired width and height. The scaling is performed in a way that the aspect ratio of the original image is kept, and eventual remaining spaces to fit the desired box are filled with transparent pixels. The original image is centered in the result image.
private BitmapDrawable scaleKeepingAspect(Resources res, int id, int dstWidth, int dstHeight) {
Bitmap b = (new BitmapFactory()).decodeResource(res, id);
float scaleX = (float) dstWidth / b.getWidth();
float scaleY = (float) dstHeight / b.getHeight();
float scale = scaleX < scaleY ? scaleX : scaleY;
int sclWidth = Math.round(scale * b.getWidth());
int sclHeight = Math.round(scale * b.getHeight());
b = Bitmap.createScaledBitmap(b, sclWidth, sclHeight, false);
int[] pixels = new int[sclWidth * sclHeight];
b.getPixels(pixels, 0, sclWidth, 0, 0, sclWidth, sclHeight);
b = Bitmap.createBitmap(dstWidth, dstHeight, b.getConfig());
b.setPixels(pixels, 0, sclWidth, (dstWidth - sclWidth) / 2, (dstHeight - sclHeight) / 2, sclWidth, sclHeight);
return new BitmapDrawable(res, Bitmap.createBitmap(b));
}
A proper place to make use of scaleKeepingAspect()
in setting the background of a View
is during layout change events, so the background is updated properly when the bounds of the View
change due to layout processing. To do so, assuming (a) your View
is identified by myView
in your layout xml, and (b) your background image is file res/drawable/background.png
, do the following:
Declare your Activity
as an implementor of View.OnLayoutChangeListener
interface:
public class MyActivity extends Activity implements View.OnLayoutChangeListener {
...
}
Register your Activity
as a listener to layout changes of your View
:
@Override public void onCreate(Bundle savedInstanceState) {
...
myView = (View) findViewById(R.id.myView);
myView.addOnLayoutChangeListener(this);
...
}
Detect for View
layout updates in the layout change handler, and update its background when needed:
@Override public void onLayoutChange (View v,
int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (left == right || top == bottom || (
left == oldLeft && top == oldTop &&
right == oldRight && bottom == oldBottom))
return;
switch (v.getId()) {
case R.id.myView:
v.setBackground(
scaleKeepingAspect(
getResources(),
R.drawable.background,
v.getWidth(),
v.getHeight()));
break;
}
}