Android: как растянуть изображение до ширины экрана при сохранении соотношения сторон?

118

Я хочу загрузить изображение (неизвестного размера, но всегда примерно квадратного) и отобразить его так, чтобы оно заполняло экран по горизонтали и растягивалось по вертикали для сохранения соотношения сторон изображения при любом размере экрана. Вот мой (нерабочий) код. Он растягивает изображение по горизонтали, но не по вертикали, поэтому оно сжимается ...

ImageView mainImageView = new ImageView(context);
    mainImageView.setImageBitmap(mainImage); //downloaded from server
    mainImageView.setScaleType(ScaleType.FIT_XY);
    //mainImageView.setAdjustViewBounds(true); 
    //with this line enabled, just scales image down
    addView(mainImageView,new LinearLayout.LayoutParams( 
            LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
fredley
источник
У всех устройств Android одинаковое соотношение ширины и высоты? В противном случае просто невозможно масштабировать изображение, чтобы оно соответствовало всей ширине / высоте, сохраняя при этом исходное соотношение ...
NoozNooz42 07
4
Нет, я не хочу, чтобы изображение заполняло экран, просто для масштабирования до ширины экрана, меня не волнует, какую часть экрана занимает изображение по вертикали, если изображение имеет правильные пропорции.
fredley 07
1
Аналогичный вопрос, хороший ответ: stackoverflow.com/questions/4677269/…
Петерис Цауне
1
"adjustViewBounds" не работал?
Darpan

Ответы:

132

Я сделал это с помощью специального представления. Установите layout_width = "fill_parent" и layout_height = "wrap_content" и укажите его на соответствующий объект для рисования:

public class Banner extends View {

  private final Drawable logo;

  public Banner(Context context) {
    super(context);
    logo = context.getResources().getDrawable(R.drawable.banner);
    setBackgroundDrawable(logo);
  }

  public Banner(Context context, AttributeSet attrs) {
    super(context, attrs);
    logo = context.getResources().getDrawable(R.drawable.banner);
    setBackgroundDrawable(logo);
  }

  public Banner(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    logo = context.getResources().getDrawable(R.drawable.banner);
    setBackgroundDrawable(logo);
  }

  @Override protected void onMeasure(int widthMeasureSpec,
      int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = width * logo.getIntrinsicHeight() / logo.getIntrinsicWidth();
    setMeasuredDimension(width, height);
  }
}
Боб Ли
источник
12
Спасибо!! Это должен быть правильный ответ! :) Я скорректировал это для большей гибкости в этом посте: stackoverflow.com/questions/4677269/… Там я использую ImageView, чтобы можно было установить возможность рисования в XML или использовать его как обычный ImageView в коде для установки изображения. Прекрасно работает!
Патрик Боос,
1
Чертов гений! это сработало как чемпион! Спасибо, что решил мою проблему с изображением баннера вверху не для всех размеров экрана! Спасибо вам большое!
JPM
1
Следите за тем, чтобы логотип не был пустым (если вы загружаете контент из Интернета). В противном случае это отлично работает, следуйте посту Патрика для фрагмента XML.
Артем Русаковский
44
Я не могу поверить, насколько сложно делать на Android то, что тривиально просто в iOS и Windows Phone.
mxcl
1
Я внес некоторые изменения, но вместо того, чтобы публиковать их в другом месте, можно ли превратить этот ответ в вики сообщества? Мои изменения - это все, что касается особых случаев, упомянутых здесь, а также возможность выбирать, какая сторона будет изменена, посредством определения макета.
Steve Pomeroy
24

В конце концов, я сгенерировал размеры вручную, что отлично работает:

DisplayMetrics dm = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getMetrics(dm);
int width = dm.widthPixels;
int height = width * mainImage.getHeight() / mainImage.getWidth(); //mainImage is the Bitmap I'm drawing
addView(mainImageView,new LinearLayout.LayoutParams( 
        width, height));
fredley
источник
Я искал такое решение, чтобы исправить свою проблему. С помощью этого расчета я успешно загрузил изображение без уменьшения соотношения сторон.
Стив,
21

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

        // Get the max possible width given our constraints
        widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);

        // Get the max possible height given our constraints
        heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);

Где hи w- размеры изображения, а p*- отступ.

А потом:

private int resolveAdjustedSize(int desiredSize, int maxSize,
                               int measureSpec) {
    ...
    switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            /* Parent says we can be as big as we want. Just don't be larger
               than max size imposed on ourselves.
            */
            result = Math.min(desiredSize, maxSize);

Таким образом, если у вас есть, layout_height="wrap_content"он будет установлен widthSize = w + pleft + pright, или, другими словами, максимальная ширина равна ширине изображения.

Это означает, что если вы не установите точный размер, изображения НИКОГДА не увеличиваются . Я считаю это ошибкой, но удачи, чтобы Google заметил или исправил ее. Изменить: Я сам написал отчет об ошибке, и они говорят, что он будет исправлен в следующем выпуске!

Другое решение

Вот еще один подкласс обходного пути, но вы должны (теоретически, я особо не тестировал его!) Иметь возможность использовать его где угодно ImageView. Чтобы использовать его, установите layout_width="match_parent", и layout_height="wrap_content". Это гораздо более общее решение, чем принятое решение. Например, вы можете подгонять как по высоте, так и по ширине.

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;

// This works around the issue described here: http://stackoverflow.com/a/12675430/265521
public class StretchyImageView extends ImageView
{

    public StretchyImageView(Context context)
    {
        super(context);
    }

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

    public StretchyImageView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        // Call super() so that resolveUri() is called.
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // If there's no drawable we can just use the result from super.
        if (getDrawable() == null)
            return;

        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

        int w = getDrawable().getIntrinsicWidth();
        int h = getDrawable().getIntrinsicHeight();
        if (w <= 0)
            w = 1;
        if (h <= 0)
            h = 1;

        // Desired aspect ratio of the view's contents (not including padding)
        float desiredAspect = (float) w / (float) h;

        // We are allowed to change the view's width
        boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;

        // We are allowed to change the view's height
        boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;

        int pleft = getPaddingLeft();
        int pright = getPaddingRight();
        int ptop = getPaddingTop();
        int pbottom = getPaddingBottom();

        // Get the sizes that ImageView decided on.
        int widthSize = getMeasuredWidth();
        int heightSize = getMeasuredHeight();

        if (resizeWidth && !resizeHeight)
        {
            // Resize the width to the height, maintaining aspect ratio.
            int newWidth = (int) (desiredAspect * (heightSize - ptop - pbottom)) + pleft + pright;
            setMeasuredDimension(newWidth, heightSize);
        }
        else if (resizeHeight && !resizeWidth)
        {
            int newHeight = (int) ((widthSize - pleft - pright) / desiredAspect) + ptop + pbottom;
            setMeasuredDimension(widthSize, newHeight);
        }
    }
}
Timmmm
источник
Я отправил отчет об ошибке . Смотрите, как это игнорируется!
Timmmm 01
3
Видимо мне приходится есть свои слова - мол, это будет исправлено в следующем выпуске!
Timmmm 02
Спасибо большое, Тим. Это должен быть последний правильный ответ. Я искал много времени, и нет ничего лучше вашего решения.!
tainy
что, если мне нужно установить maxHeight при использовании вышеупомянутого StretchyImageView. Я не нашел для этого решения.
tainy
17

Установка для adjustViewBounds значения true и использование группы представлений LinearLayout сработали для меня очень хорошо. Нет необходимости создавать подклассы или запрашивать показатели устройства:

//NOTE: "this" is a subclass of LinearLayout
ImageView splashImageView = new ImageView(context);
splashImageView.setImageResource(R.drawable.splash);
splashImageView.setAdjustViewBounds(true);
addView(splashImageView);
Мэтт Стокер
источник
1
... или используя свойство ImageView XML - android: adjustViewBounds = "true"
Анатолий Шуба
Отлично! Это было так просто, и это идеально растягивает изображение. Разве это не правильный ответ? Ответ с CustomView у меня не сработал (какая-то странная ошибка xml)
user3800924,
13

Я боролся с этой проблемой в той или иной форме для AGES, спасибо, спасибо, СПАСИБО .... :)

Я просто хотел указать, что вы можете получить обобщаемое решение из того, что сделал Боб Ли, просто расширив View и переопределив onMeasure. Таким образом, вы можете использовать это с любым чертежом, которое хотите, и оно не сломается, если нет изображения:

    public class CardImageView extends View {
        public CardImageView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }

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

        public CardImageView(Context context) {
            super(context);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            Drawable bg = getBackground();
            if (bg != null) {
                int width = MeasureSpec.getSize(widthMeasureSpec);
                int height = width * bg.getIntrinsicHeight() / bg.getIntrinsicWidth();
                setMeasuredDimension(width,height);
            }
            else {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
duhrer
источник
2
Из многих, многих решений этой проблемы это первое, которое представляет собой простое решение, в котором у других всегда есть недостаток (например, невозможность изменить изображение во время выполнения без перезаписи кода). Спасибо!
MacD
Это чистая прелесть - мы уже некоторое время боролись с этим, и все очевидные решения с использованием атрибутов xml никогда не работали. При этом я мог просто заменить свой ImageView на расширенный вид, и все заработало, как ожидалось!
slott
Это абсолютно лучшее решение данной проблемы.
erlando
Это отличный ответ. Вы можете без проблем использовать этот класс в XML-файле: <com.yourpackagename.CardImageView android: layout_width = "fill_parent" android: layout_height = "wrap_content" android: background = "@ drawable / your_photo" />. Большой!
arniotaki
11

В некоторых случаях эта волшебная формула прекрасно решает проблему.

Для тех, кто борется с этим на другой платформе, опция «размер и форма» прекрасно реализована в Android, но ее трудно найти.

Обычно вам нужна такая комбинация:

  • ширина соответствует родительскому элементу,
  • высота обтекания содержимого,
  • AdjustViewBounds включен (sic)
  • масштаб fitCenter
  • CropToPadding ВЫКЛ. (sic)

Тогда это автоматически и потрясающе.

Если вы разработчик iOS, совершенно удивительно, как просто в Android вы можете делать «полностью динамические высоты ячеек» в табличном представлении ... эээ, я имею в виду ListView. Наслаждаться.

<com.parse.ParseImageView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/post_image"
    android:src="@drawable/icon_192"
    android:layout_margin="0dp"
    android:cropToPadding="false"
    android:adjustViewBounds="true"
    android:scaleType="fitCenter"
    android:background="#eff2eb"/>

введите описание изображения здесь

Fattie
источник
6

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

<FrameLayout
     android:layout_width="match_parent"
     android:layout_height="wrap_content">

     <ImageView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:adjustViewBounds="true"
          android:scaleType="centerCrop"
          android:src="@drawable/whatever" />
</FrameLayout>
NeilDA
источник
Извините, это не работает. Он масштабирует ширину, но centerCrop не поддерживает соотношение сторон.
ricosrealm
1
После многих часов борьбы с пользовательскими классами, расширяющими ImageView (как написано во многих ответах и ​​статьях) и имеющими исключение типа «Следующие классы не могут быть найдены», я удалил этот код. Внезапно я заметил, что проблема в моем ImageView была в фоновом атрибуте! Изменил его на, srcи ImageView стал пропорциональным.
CoolMind
5

Я сделал это с этими значениями в LinearLayout:

Scale type: fitStart
Layout gravity: fill_horizontal
Layout height: wrap_content
Layout weight: 1
Layout width: fill_parent
helduel
источник
Не могли бы вы привести реальный пример? Где мне поместить эти значения в мой XML-файл?
John Smith Optional
5

Все делают это программно, поэтому я подумал, что этот ответ здесь идеально подходит. Этот код работал у меня в xml. Я еще НЕ думаю о соотношении, но все же хотел разместить этот ответ, если он кому-то поможет.

android:adjustViewBounds="true"

Ура ..

Синдри Тор
источник
3

Очень простое решение - просто использовать функции, предоставляемые RelativeLayout.

Вот xml, который делает это возможным со стандартным Android Views:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true">

    <RelativeLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
        <LinearLayout
            android:id="@+id/button_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_alignParentBottom="true"
            >
            <Button
                android:text="button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            <Button
                android:text="button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            <Button
                android:text="button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </LinearLayout>
        <ImageView 
            android:src="@drawable/cat"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:adjustViewBounds="true"
            android:scaleType="centerCrop"
            android:layout_above="@id/button_container"/>
    </RelativeLayout>
</ScrollView>

Хитрость в том, что вы устанавливаете, ImageViewчтобы заполнить экран, но он должен быть выше других макетов. Так вы добьетесь всего, что вам нужно.

Warpzit
источник
2

Его простой вопрос настройки adjustViewBounds="true"и scaleType="fitCenter"в файле XML для ImageView!

<ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:src="@drawable/image"

        android:adjustViewBounds="true"
        android:scaleType="fitCenter"
        />

Примечание: layout_widthустановлен наmatch_parent

Кошик Н.П.
источник
1

Вы устанавливаете ScaleType на ScaleType.FIT_XY. Согласно javadocs , это растянет изображение до размеров всей области, при необходимости изменив соотношение сторон. Это объяснило бы поведение, которое вы наблюдаете.

Чтобы получить желаемое поведение ... FIT_CENTER, FIT_START или FIT_END близки, но если изображение уже, чем его высота, оно не начнет заполнять ширину. Однако вы могли бы посмотреть, как они реализованы, и, вероятно, сможете выяснить, как настроить это для своих целей.

Шерил Саймон
источник
2
Я пробовал FIT_CENTER, но не другие. К сожалению, они тоже не работают, если для setAdjustViewBounds установлено значение true и false. Есть ли способ получить пиксельную ширину экрана устройства, а также ширину / высоту загруженного изображения и вручную установить размер?
Фредли 08
1

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

P.Melch
источник
1

Посмотрите, есть гораздо более простое решение вашей проблемы:

ImageView imageView;

protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.your_layout);
    imageView =(ImageView)findViewById(R.id.your_imageView);
    Bitmap imageBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.your_image);
    Point screenSize = new Point();
    getWindowManager().getDefaultDisplay().getSize(screenSize);
    Bitmap temp = Bitmap.createBitmap(screenSize.x, screenSize.x, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(temp);
    canvas.drawBitmap(imageBitmap,null, new Rect(0,0,screenSize.x,screenSize.x), null);
    imageView.setImageBitmap(temp);
}
user3673209
источник
1

Вы можете использовать мой StretchableImageView, сохраняя соотношение сторон (по ширине или по высоте) в зависимости от ширины и высоты чертежа:

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;

public class StretchableImageView extends ImageView{

    public StretchableImageView(Context context) {
        super(context);
    }

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

    public StretchableImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if(getDrawable()!=null){
            if(getDrawable().getIntrinsicWidth()>=getDrawable().getIntrinsicHeight()){
                int width = MeasureSpec.getSize(widthMeasureSpec);
                int height = width * getDrawable().getIntrinsicHeight()
                            / getDrawable().getIntrinsicWidth();
                setMeasuredDimension(width, height);
            }else{
                int height = MeasureSpec.getSize(heightMeasureSpec);
                int width = height * getDrawable().getIntrinsicWidth()
                            / getDrawable().getIntrinsicHeight();
                setMeasuredDimension(width, height);
            }
        }
    }
}
valerybodak
источник
0

Для меня android: scaleType = "centerCrop" не решил мою проблему. Это на самом деле еще больше расширило изображение. Я попробовал с android: scaleType = "fitXY", и он отлично сработал.

Рикардо
источник
0

Это работает нормально в соответствии с моим требованием

<ImageView android:id="@+id/imgIssue" android:layout_width="fill_parent" android:layout_height="wrap_content" android:adjustViewBounds="true" android:scaleType="fitXY"/>

Ананд Савджани
источник