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

чи всі пристрої Android мають однакове співвідношення ширини / висоти? Якщо ні, просто неможливо масштабувати зображення відповідно до всієї ширини / висоти, зберігаючи початкове співвідношення ...
NoozNooz42

4
Ні, я не хочу, щоб зображення заповнювало екран, просто масштабувати до ширини екрана, мені байдуже, яка частина екрана зображення займає вертикально, доки зображення буде у правильних пропорціях.
Фредлі

1
Подібне запитання, хороша відповідь: stackoverflow.com/questions/4677269/…
Pēteris Caune

1
"налаштуванняViewBounds" не працює?
Дарпан

Відповіді:


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
Я вніс деякі зміни, але замість того, щоб публікувати їх десь в іншому місці, чи можна було б цю відповідь перетворити на вікі спільноти? Мої зміни - це все, що стосується конкретних випадків, згаданих тут, а також можливість вибору, на яку сторону буде змінено розмір шляхом визначення макета.
Стів Померой

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

Я шукав таке рішення, щоб виправити свою проблему. За допомогою цього розрахунку я отримав успішне завантаження зображення без зменшення співвідношення сторін.
Стів

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

3
Мабуть, я повинен їсти свої слова - вони кажуть, що це було зафіксовано у майбутньому випуску!
Timmmm

Дуже дякую Тіме. Це має бути остаточна правильна відповідь. Я шукав багато часу і нічого кращого за ваше рішення.
тайний

що робити, якщо мені потрібно встановити maxHeight при використанні вищевказаного StretchyImageView. Я не знайшов для цього жодного рішення.
тайний

17

Встановлення trueViewBounds на істинне та використання групи перегляду 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: prilagodViewBounds = "true"
Анатолій Шуба

Ідеально! Це було так просто і це розтягує зображення ідеально. Шлях ця відповідь не є правильною? Відповідь з користувачем CustomView не працював для мене (якась дивна помилка xml)
user3800924

13

Я боровся з цією проблемою в тій чи іншій формі для AGES, дякую, дякую, ДЯКУЮ .... :)

Я просто хотів зазначити, що ви можете отримати узагальнене рішення з того, що робив Боб Лі, просто розширивши Перегляд і змінивши 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);
            }
        }
    }

2
З усіх безлічі рішень цієї проблеми, це перше, що є лише рішенням, що випадає, де інші завжди мають недолік (наприклад, не в змозі змінити зображення під час виконання без повторного запису коду). Дякую!
MacD

Це чиста приголомшлива ситуація - бореться з цим вже деякий час, і всі очевидні рішення з використанням атрибутів xml ніколи не працювали. Цим я міг просто замінити свій ImageView розширеним видом, і все працювало так, як очікувалося!
слот

Це абсолютно найкраще рішення цієї проблеми.
erlando

Це чудова відповідь. Ви можете використовувати цей клас у XML-файлі без побоювань, як-от так: <com.yourpackagename.CardImageView android: layout_width = "fill_parent" android: layout_height = "wrap_content" android: background = "@ dravable / your_photo" />. Чудово!
arniotaki

11

У деяких випадках ця магічна формула прекрасно вирішує проблему.

Для тих, хто бореться з цим, що надходить з іншої платформи, "розмір і форма, яка підходить" варіант в Android прекрасно обробляється, але це важко знайти.

Зазвичай ви хочете цю комбінацію:

  • батьківська відповідність по ширині,
  • вміст обгортання по висоті,
  • налаштуватиViewBounds увімкнено (sic)
  • масштаб центр
  • cropToPadding OFF (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"/>

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


6

Мені вдалося досягти цього лише за допомогою цього XML-коду. Може статися так, що затемнення не надає висоти, щоб показати, що воно розширюється, щоб відповідати; однак, коли ви фактично запускаєте це на пристрої, він належним чином відображає та забезпечує бажаний результат. (ну принаймні для мене)

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

Вибачте, це не працює. Він масштабує ширину, але 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

Чи можете ви навести реальний приклад? Де я можу помістити ці значення у свій XML-файл?
Джон Сміт Необов’язково

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для заповнення екрана, але він повинен бути вище інших макетів. Таким чином ви досягаєте всього необхідного.


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. Чи все-таки можна отримати піксельну ширину екрану пристрою, а також ширину / висоту завантаженого зображення та встановити розмір вручну?
fredley

1

ScaleType.CENTER_CROP зробить все, що завгодно: розтягнеться на повну ширину і відповідно масштабує висоту. якщо масштабована висота перевищує межі екрана, зображення буде обрізане.


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

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

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"/>

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.