Как получить полноэкранное фоновое изображение (с центральным обрезкой), которое не изменяет размер

Я пытаюсь установить фотографию в качестве фона Activity. Я хочу, чтобы фон был полноэкранным изображением (без границ).

Как бы я хотел, чтобы изображение заполняло весь фон активности без растягивания/сжатия (т.е. непропорциональное масштабирование для X и Y), и я не возражаю, если фотография должна быть обрезана, я использую RelativeLayout с a ImageViewandroid:scaleType="centerCrop"), а остальная часть моего макета состоит из ScrollView и его дочерних элементов.

<!-- Tried this with a FrameLayout as well... -->
<RelativeLayout> 
   <!-- Background -->
   <ImageView  
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:scaleType="centerCrop"/>
   <!-- Form -->
   <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent">
      <ScrollView>
        <LinearLayout>
          ...
          <EditText/>
        </LinearLayout>
      </ScrollView>
   </LinearLayout>
</RelativeLayout>

Проблема в том, что в остальной части макета есть несколько представлений EditText, и когда появляется экранная клавиатура, ImageView получает размер. Я хотел бы, чтобы фон оставался прежним, независимо от того, видна ли панель клавиатуры или нет.

Я видел множество вопросов относительно SO о том, что ImageViews повторно размер, но (imo) нет удовлетворительных ответов. Большинство из них состоят только в установке android:windowSoftInputMode="adjustPan", что не всегда практично, особенно если вы хотите, чтобы пользователь мог прокручивать в своей деятельности - или используя getWindow().setBackgroundDrawable(), который не обрезает изображение.

Мне удалось подкласса ImageView и переопределить его onMeasure() (см. мой ответ здесь: ImageView изменяет размер при открытии клавиатуры), чтобы он мог заставить фиксированная высота и ширина - равные размеру экрана устройства - в соответствии с флагом, но мне интересно, есть ли лучший способ достичь желаемого результата.

Итак, чтобы подвести итог, мой вопрос: Как установить фоном активности в полноэкранную фотографию

  • с типом масштаба = "centerCrop", так что фотография масштабируется равномерно (поддерживая ее соотношение сторон), и поэтому обе размеры (ширина и высота) будут равны или больше соответствующего размера представления;

  • , который не изменяется при появлении экранной клавиатуры;


ОТВЕТ:

Я закончил следующий @pskink совет и подклассифицировал BitmapDrawable (см. его ответ ниже). Я должен был сделать некоторые настройки, чтобы убедиться, что BackgroundBitmapDrawable всегда масштабируется и обрезается таким образом, который заполняет экран.

Вот мой последний класс, адаптированный из его ответа:

public class BackgroundBitmapDrawable extends BitmapDrawable {
    private Matrix mMatrix = new Matrix();
    private int moldHeight;
    boolean simpleMapping = false;

    public BackgroundBitmapDrawable(Resources res, Bitmap bitmap) {
        super(res, bitmap);
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        if (bounds.height() > moldHeight) {
            moldHeight = bounds.height();
            Bitmap b = getBitmap();
            RectF src = new RectF(0, 0, b.getWidth(), b.getHeight());
            RectF dst;

            if (simpleMapping) {
                dst = new RectF(bounds);
                mMatrix.setRectToRect(src, dst, ScaleToFit.CENTER);
            } else {
                // Full Screen Image -> Always scale and center-crop in order to fill the screen
                float dwidth = src.width();
                float dheight = src.height();

                float vwidth = bounds.width(); 
                float vheight = bounds.height();

                float scale;
                float dx = 0, dy = 0;

                if (dwidth * vheight > vwidth * dheight) {
                    scale = (float) vheight / (float) dheight; 
                    dx = (vwidth - dwidth * scale) * 0.5f;
                } else {
                    scale = (float) vwidth / (float) dwidth;
                    dy = (vheight - dheight * scale) * 0.5f;
                }

                mMatrix.setScale(scale, scale);
                mMatrix.postTranslate(dx, dy);

            }
        }
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.drawColor(0xaa00ff00);
        canvas.drawBitmap(getBitmap(), mMatrix, null);
    }
}

Тогда это просто вопрос создания BackgroundBitmapDrawable и установки его в качестве корня View background.

Ответы

Ответ 1

попробуйте этот LayerDrawable (res/drawable/backlayer.xml):

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
>
    <item>
        <shape>
        <solid android:color="#f00" />
        </shape>
    </item>
<item>
        <bitmap
        android:gravity="top" android:src="@drawable/back1">
        </bitmap>
    </item>
</layer-list>

и установите его на макет верхнего уровня: android: background = "@drawable/backlayer"

UPDATE: попробуйте этот BitmapDrawadle, установите его на макет верхнего уровня (setBackgroundDrawable()), если simpleMapping == true достаточно, вы можете удалить ветку else:

class D extends BitmapDrawable {
    private Matrix mMatrix = new Matrix();
    private int moldHeight;

    public D(Resources res, Bitmap bitmap) {
        super(res, bitmap);
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        if (bounds.height() > moldHeight) {
            moldHeight = bounds.height();
            Bitmap b = getBitmap();
            RectF src = new RectF(0, 0, b.getWidth(), b.getHeight());
            RectF dst;

            // if simpleMapping is good enough then remove "else" branch and
            // declare "dst" as:
            // RectF dst = new RectF(bounds);
            boolean simpleMapping = true;
            if (simpleMapping) {
                dst = new RectF(bounds);
            } else {
                float x = bounds.exactCenterX();
                dst = new RectF(x, 0, x, bounds.height());
                float scale = bounds.height() / src.height();
                float dx = scale * src.width() / 2;
                dst.inset(-dx, 0);
            }
            mMatrix.setRectToRect(src, dst, ScaleToFit.CENTER);
        }
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.drawColor(0xaa00ff00);
        canvas.drawBitmap(getBitmap(), mMatrix, null);
    }
}

Ответ 2

Поместите все в FrameLayout, с первым ребенком образное изображение, которое будет соответствовать ему parent (frameLayout), и вы настроите его так, как вы хотите, и перед представлением изображения вы поместите все, что захотите (LinearLayout я предположим)

Ответ 3

После поцарапания моей головы какое-то время это было моим "хорошим на данный момент" решением.

Обмен образцом XML, который может быть полезен для всех.

<!-- RelativeLayout so that Views can overlap each other.
     I believe a FrameLayout would work as well. -->
<RelativeLayout
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <!-- I've put the ImageView inside 
         a ScrollView with android:fillViewport="true" and android:isScrollContainer="false" -->
    <ScrollView
        android:id="@+id/backgroundScrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true"
        android:isScrollContainer="false" > 

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" >

            <!-- The FullScreen, Center-Cropped Image Background --> 
            <ImageView
                android:id="@+id/backgroundImage"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@drawable/background_image" />
        </LinearLayout>
    </ScrollView>

    <!-- The rest of the Views, containing the data entry form... -->
    <LinearLayout
        android:id="@+id/form"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

            <ScrollView
                android:id="@+id/formScrollView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" >

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:layout_weight="1"
                    android:orientation="vertical" >

                    <!-- The "Form", with EditTexts, Checkboxes and whatnot... -->

                </LinearLayout>
            </ScrollView>

    </LinearLayout>
</RelativeLayout>

В этом макете я выполняю две вещи:

  • formScrollView, который содержит "форму" с EditTexts и т.д., получает размер (как я этого хочу) и прокручивается, когда появляется экранная клавиатура.

  • backgroundScrollView, который содержит ImageView, не получает редизайн (из-за android:isScrollContainer="false") и заполняет весь видовой экран (android:fillViewport="true"). Таким образом, фон остается тем же самым независимо от того, видна ли клавиатура или нет.

Не идеальное решение, так как ImageView каким-то образом страдает очень небольшим изменением в его размере, но результат пока достаточно хорош. Если кто-то знает лучшее решение, сообщите мне.

Ответ 4

Решение в ответе является лучшим, чтобы использовать его:

Resources res = getActivity().getResources();
Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.some_image);
BackgroundBitmapDrawable background = new BackgroundBitmapDrawable(res, bitmap);
view.setBackgroundDrawable(background);

или

view.setBackground(background);

для API 16 и выше.

Ответ 5

Измените свой ScaleType

 android:scaleType="FitXY"

и в вашем манифесте

<activity
           android:name=".activity.MainActivity"
            android:windowSoftInputMode="adjustPan|stateHidden" />

Ответ 6

Создайте ImageView в своем xml. Поместите его ниже вашего тега Layout. Установите переводZ на положительное значение, например 10dp, и scaleType на centerCrop.