Android - сохранить соотношение сторон фонового изображения на кнопке по умолчанию

Есть ли способ сохранить соотношение сторон чертежа при применении в качестве фона на Button (нельзя использовать ImageButton (нужна возможность для текста на Button), нельзя использовать setCompoundDrawables(), потому что это также не соответствует моим требованиям (я сделал свой собственный класс для поддержки добавления фона, сохраняя при этом исходную кнопку по умолчанию для Android, плюс мне нужно установить фон в середине кнопки, а не слева, справа, сверху или снизу)).

Я попробовал решение с ScaleDrawable, но либо я что-то делаю не так, либо оно не работает:

Bitmap scaled = Bitmap.createScaledBitmap(original, (int)newBmpWidth, (int)newBmpHeight, false);
BitmapDrawable bd = new BitmapDrawable(scaled);
ScaleDrawable sd = new ScaleDrawable(bd, Gravity.CENTER, a, b);
//for a and b I tried lots of different values, ranging from 0.07f to 1000
sd.setLevel(1);

Затем я использую ScaleDrawable в качестве фона, но он все равно растягивается.

Я также пробовал InsetDrawable, но опять же, либо я что-то не так делаю, либо он не работает, ожидая первого:

BitmapDrawable mBitmapDrawable = new BitmapDrawable(mBitmap);
InsetDrawable mInsetDrawable = new InsetDrawable(mBitmapDrawable, mLeftOffset, 0, mRightOffset, 0);

Затем я использую InsetDrawable в качестве фона, но он все равно растягивается.

Есть ли способ применить фон, но сохранить соотношение сторон на Button? Я могу найти множество ответов для ImageButtons, но, к сожалению, для Button нет методов setImageBitmap() или setScaleType().


person stealthjong    schedule 31.10.2012    source источник
comment
какое разрешение (в пикселях) у вашего изображения? и вы тоже хотите фон по умолчанию для Button?   -  person Gagan    schedule 31.10.2012
comment
@Gagan Это зависит от того, что я пробовал, от 40x30 до 256x256 пикселей. И да.   -  person stealthjong    schedule 31.10.2012
comment
Обычно ширина Buttons меняется, но высота остается постоянной. Так что будет довольно сложно сохранить соотношение сторон вашего изображения нетронутым.   -  person Gagan    schedule 31.10.2012
comment
@Gagan Хорошо, ImageButton может делать все вышеперечисленное, включая масштабирование и сохранение соотношения сторон, но я не могу применить к ним текст. Я пытаюсь сейчас, если я могу просто нарисовать текст на холсте и сделать его ImageButtons   -  person stealthjong    schedule 31.10.2012


Ответы (1)


Недавно я столкнулся с той же проблемой при установке фонового изображения файла ImageView. Решение, которое я придумал, работает с любым View, поэтому оно должно охватывать и Button.

Проблема в том, что View всегда растягивает свой фон на всю ширину и высоту View, независимо от масштабирования Drawable, предоставленного в качестве фона. Как следует из вашего отчета о неспособности предоставить специализированные Drawables, этому нельзя противодействовать никакими средствами масштабирования изображения перед установкой его в качестве фона, потому что View просто берет размеры изображения, какими бы они ни были, и масштабирует их снова независимо, чтобы заполнить View. область.

В моем решении используется другой подход, основанный на том факте, что если мы можем предоставить фоновое изображение с тем же соотношением сторон, что и у View, искажений не будет.

Функция scaleKeepingAspect() ниже принимает ресурс изображения и отвечает на BitmapDrawable, который содержит исходное изображение, масштабированное до желаемой ширины и высоты. Масштабирование выполняется таким образом, чтобы соотношение сторон исходного изображения сохранялось, а возможные оставшиеся места для размещения в нужном поле заполнялись прозрачными пикселями. Исходное изображение центрируется в результирующем изображении.

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));

}

Правильное место для использования scaleKeepingAspect() при настройке фона View — это во время событий изменения макета, поэтому фон обновляется должным образом, когда границы View изменяются из-за обработки макета. Для этого, предполагая, что (а) ваш View идентифицируется как myView в вашем макете xml, и (б) ваше фоновое изображение — это файл res/drawable/background.png, сделайте следующее:

  1. Объявите свой Activity реализатором интерфейса View.OnLayoutChangeListener:

    public class MyActivity extends Activity implements View.OnLayoutChangeListener {
    
        ...
    
    }
    
  2. Зарегистрируйте свой Activity в качестве слушателя изменений макета вашего View:

    @Override public void onCreate(Bundle savedInstanceState) {
    
        ...
    
        myView = (View) findViewById(R.id.myView);
        myView.addOnLayoutChangeListener(this);
    
        ...
    
    }
    
  3. Обнаружение View обновлений макета в обработчике изменения макета и обновление его фона при необходимости:

    @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;
    
        }
    
    }
    
person coolparadox    schedule 08.03.2016
comment
Это сработало для меня, но результат был действительно пиксельным. Хороший код, однако. - person arlomedia; 04.05.2018
comment
Хорошо знать; кажется, что createScaledBitmap при использовании предпочитает скорость, а не качество. (В своих тестах я не заметил пикселизации; возможно, потому, что в то время я работал с изображениями с высоким разрешением?) В любом случае вы можете изменить параметр фильтра на true и посмотреть, улучшит ли это ваши результаты. - person coolparadox; 05.05.2018