Android ImageView масштабирует меньшее изображение по ширине с гибкой высотой без обрезки или искажения

79

Часто спрашивают, но никогда не отвечают (по крайней мере, в воспроизводимой форме).

У меня есть представление изображения с изображением, которое меньше, чем представление. Я хочу масштабировать изображение по ширине экрана и настроить высоту ImageView, чтобы отразить пропорционально правильную высоту изображения.

<ImageView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
/>

В результате изображение будет центрировано в исходном размере (меньше ширины экрана) с полями по бокам. Не хорошо.

Я добавил

android:adjustViewBounds="true" 

Тот же эффект, ничего хорошего. я добавил

android:scaleType="centerInside"

Тот же эффект, ничего хорошего. Я перешел centerInsideна fitCenter. Тот же эффект, ничего хорошего. Я перешел centerInsideна centerCrop.

android:scaleType="centerCrop"

Теперь, наконец, изображение масштабируется по ширине экрана, но обрезается сверху и снизу! Я перешел centerCropна fitXY.

android:scaleType="fitXY"

Теперь изображение масштабируется по ширине экрана, но не масштабируется по оси Y, что приводит к искажению изображения.

Удаление не android:adjustViewBounds="true"имеет никакого эффекта. Добавление android:layout_gravity, как предлагалось в другом месте, снова не имеет никакого эффекта.

Я пробовал другие комбинации - безрезультатно. Итак, пожалуйста, кто-нибудь знает:

Как настроить XML ImageView для заполнения ширины экрана, масштабирования меньшего изображения для заполнения всего вида, отображения изображения с его соотношением сторон без искажения или обрезки?

РЕДАКТИРОВАТЬ: Я также попытался установить произвольную числовую высоту. Это влияет только на centerCropнастройку. Изображение будет искажено по вертикали в соответствии с высотой обзора.

Mundi
источник
Вы пробовали android:scaleType="fitCenter"?
Майкл Сели
1
@ BrainSlugs83 У меня есть, и он все еще работает для меня. Кроме того, был задан вопрос, чтобы определить, что попытался задать вопрос. Нет необходимости в язвительности, особенно через семь месяцев после публикации.
Майкл Сели 01
Это не работает. -- Смотри ниже; Кроме того, я пробовал и могу убедиться, что он не работает (если вы пробовали и видите разные результаты, обсудите ниже и покажите нам, что мы делаем не так - единственный вывод, к которому я могу прийти, это то, что вы неправильно поняли вопрос, или еще не пробовали).
BrainSlugs83 02

Ответы:

110

Я решил это, создав java-класс, который вы включаете в свой файл макета:

public class DynamicImageView extends ImageView {

    public DynamicImageView(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        final Drawable d = this.getDrawable();

        if (d != null) {
            // ceil not round - avoid thin vertical gaps along the left/right edges
        final int width = MeasureSpec.getSize(widthMeasureSpec);
        final int height = (int) Math.ceil(width * (float) d.getIntrinsicHeight() / d.getIntrinsicWidth());
            this.setMeasuredDimension(width, height);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
}

Теперь вы используете это, добавив свой класс в свой файл макета:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <my.package.name.DynamicImageView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:scaleType="centerCrop"
        android:src="@drawable/about_image" />
</RelativeLayout>
Марк Мартинссон
источник
4
+1 Отличное решение, отлично работает и для Nexus 7. И самое приятное то, что оно также правильно работает в редакторе макетов в Eclipse.
Ridcully
Работает на Galaxy S4. Почему бы и нет :) THx
Malachiasz
Благодарю. Отличное решение. Достаточно легко изменить, чтобы сделать то же самое на основе heightMeasureSpec.
speedynomads 07
1
Да, но нет ... Это решение размывает изображения. Если я использую ImageView и fitCenter с фиксированной высотой, изображение не размывается (но фиксированная высота для меня не является решением). Как избежать размытия?
Geltrude
Я искал это решение в течение нескольких недель, спасибо! Я хочу повысить масштаб одного изображения, но это отлично работает
Соко
36

У меня была такая же проблема, как и у вас. После 30 минут пробования разных вариантов я решил проблему таким образом. Это дает вам гибкую высоту и ширину, адаптированную к родительской ширине

<ImageView
            android:id="@+id/imageview_company_logo"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:adjustViewBounds="true"
            android:scaleType="fitCenter"
            android:src="@drawable/appicon" />
haydarkarrar
источник
4
Ключевым моментом здесь является использование adjustViewBounds
user3690202
@ user3690202 вы правы, это тоже решило мою проблему. Спасибо
Парт Патель
22

В рамках стандарта макета XML нет жизнеспособного решения.

Единственный надежный способ реагировать на размер динамического изображения - использовать его LayoutParamsв коде.

Неутешительно.

Mundi
источник
8
  android:scaleType="fitCenter"

Вычислите масштаб, который будет поддерживать исходное соотношение сторон src, но также гарантирует, что src полностью помещается в dst. По крайней мере одна ось (X или Y) подойдет точно. Результат сосредоточен внутри dst.

редактировать:

Проблема здесь в том, что изображение layout_height="wrap_content"не «позволяет» расширяться. Вам нужно будет установить размер для этого изменения

  android:layout_height="wrap_content"

к

  android:layout_height="100dp"  // or whatever size you want it to be

edit2:

работает отлично:

<ImageView
    android:id="@+id/imageView1"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:scaleType="fitCenter"
    android:src="@drawable/img715945m" />
Будиус
источник
2
В результате получается первая версия изображения: центрированная, не расширенная по горизонтали, с полями с обеих сторон. Не хорошо.
Mundi
2
Это не работает. В результате получается изображение с точной высотой, но не растянутым по горизонтали. Не хорошо. Также см. Мою правку в вопросе.
Mundi
1
nasa.gov/images/content/715945main__NOB0093_4x3_516-387.jpg Важная часть заключается в том, что он кажется меньше, чем экран Nexus 7.
Mundi
2
Не хорошо. Высота не знаю - она ​​динамичная. Это приведет к непредсказуемым искажениям. Редактирование в моем вопросе уже охватывает это. Извините, но спасибо за ваши усилия! Позор Google!
Mundi
1
Я знал, что это вариант, но искал решение XML . Тем не менее, вы должны отредактировать это в своем ответе. Благодаря!
Mundi
8

Это небольшое дополнение к отличному решению Марка Мартинссона.

Если ширина вашего изображения больше его высоты, то решение Марка оставит пространство вверху и внизу экрана.

Приведенное ниже исправляет это, сначала сравнивая ширину и высоту: если ширина изображения> = height, тогда оно масштабирует высоту в соответствии с высотой экрана, а затем масштабирует ширину, чтобы сохранить соотношение сторон. Точно так же, если высота изображения> ширины, тогда оно будет масштабировать ширину в соответствии с шириной экрана, а затем масштабировать высоту, чтобы сохранить соотношение сторон.

Другими словами, он полностью удовлетворяет определению scaleType="centerCrop":

http://developer.android.com/reference/android/widget/ImageView.ScaleType.html

Равномерно масштабируйте изображение (сохраняйте соотношение сторон изображения) так, чтобы оба размера (ширина и высота) изображения были равны или превышали соответствующий размер представления (без заполнения).

package com.mypackage;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;

public class FixedCenterCrop extends ImageView
{
    public FixedCenterCrop(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)
    {
        final Drawable d = this.getDrawable();

        if(d != null) {
            int height = MeasureSpec.getSize(heightMeasureSpec);
            int width = MeasureSpec.getSize(widthMeasureSpec);

            if(width >= height)
                height = (int) Math.ceil(width * (float) d.getIntrinsicHeight() / d.getIntrinsicWidth());
            else
                width = (int) Math.ceil(height * (float) d.getIntrinsicWidth() / d.getIntrinsicHeight());

            this.setMeasuredDimension(width, height);

        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
}

Это решение автоматически работает в портретном или ландшафтном режиме. Вы ссылаетесь на него в своем макете так же, как в решении Марка. Например:

<com.mypackage.FixedCenterCrop
    android:id="@+id/imgLoginBackground"
    android:src="@drawable/mybackground"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:scaleType="centerCrop" />
Майкл Кристофф
источник
3

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

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/image"
        android:layout_width="0px"
        android:layout_height="180dip"
        android:layout_weight="1.0"
        android:adjustViewBounds="true"
        android:scaleType="fitStart" />

</LinearLayout>
tbm
источник
Это решение отлично сработало для меня. Это позволило мне задать моему ImageView определенную высоту, а ImageView динамически регулировать его ширину, чтобы сохранить его соотношение сторон.
Люк
0

Котлинская версия ответа Марка Мартинссона :

class DynamicImageView(context: Context, attrs: AttributeSet) : AppCompatImageView(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val drawable = this.drawable

    if (drawable != null) {
        //Ceil not round - avoid thin vertical gaps along the left/right edges
        val width = View.MeasureSpec.getSize(widthMeasureSpec)
        val height = Math.ceil((width * drawable.intrinsicHeight.toFloat() / drawable.intrinsicWidth).toDouble()).toInt()
        this.setMeasuredDimension(width, height)
    } else {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }
}
нгу
источник
0

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

public class DynamicImageView extends ImageView {

    final int MAX_SCALE_FACTOR = 2;
    public DynamicImageView(final Context context) {
        super(context);
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        final Drawable d = this.getDrawable();

        if (d != null) {
            // ceil not round - avoid thin vertical gaps along the left/right edges
            int width = View.MeasureSpec.getSize(widthMeasureSpec);
            if (width > (d.getIntrinsicWidth()*MAX_SCALE_FACTOR)) width = d.getIntrinsicWidth()*MAX_SCALE_FACTOR;
            final int height = (int) Math.ceil(width * (float) d.getIntrinsicHeight() / d.getIntrinsicWidth());
            this.setMeasuredDimension(width, height);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
}
азмат
источник