Android: Comment étirer une image à la largeur de l'écran tout en conservant le rapport hauteur / largeur?

118

Je veux télécharger une image (de taille inconnue, mais qui est toujours à peu près carrée) et l'afficher de manière à ce qu'elle remplisse l'écran horizontalement et s'étire verticalement pour maintenir le rapport hauteur / largeur de l'image, quelle que soit la taille de l'écran. Voici mon code (non fonctionnel). Il étire l'image horizontalement, mais pas verticalement, donc elle est écrasée ...

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
la source
tous les appareils Android ont-ils exactement le même rapport largeur / hauteur? Sinon, il est tout simplement impossible de redimensionner une image pour l'adapter à toute la largeur / hauteur tout en préservant le rapport d'origine ...
NoozNooz42
4
Non, je ne veux pas que l'image remplisse l'écran, juste à l'échelle de la largeur de l'écran, je me fiche de la quantité d'écran que l'image occupe verticalement, tant que l'image est dans les bonnes proportions.
fredley
1
Question similaire, bonne réponse: stackoverflow.com/questions/4677269/…
Pēteris Caune
1
«AdjustViewBounds» n'a-t-il pas fonctionné?
Darpan

Réponses:

132

J'ai accompli cela avec une vue personnalisée. Définissez layout_width = "fill_parent" et layout_height = "wrap_content", et pointez-le vers le dessinable approprié:

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);
  }
}
Bob Lee
la source
12
Je vous remercie!! Cela devrait être la bonne réponse! :) J'ai ajusté cela pour un peu plus de flexibilité dans ce post: stackoverflow.com/questions/4677269/… Là, j'utilise un ImageView afin que l'on puisse définir le dessinable dans le XML ou l'utiliser comme ImageView normale dans le code pour définir l'image. Fonctionne très bien!
Patrick Boos
1
Génie Freakin! qui a fonctionné comme un champion! Merci, il a résolu mon problème avec une bannière d'image en haut pas à droite dans toutes les tailles d'écran! Merci beaucoup!
JPM
1
Faites attention au logo étant nul (si vous téléchargez du contenu sur Internet). Sinon, cela fonctionne très bien, suivez le post de Patrick pour l'extrait de code XML.
Artem Russakovskii
44
Je ne peux pas croire à quel point il est difficile de faire des choses sur Android qui sont trivialement faciles dans iOS et Windows Phone.
mxcl
1
J'ai fait quelques changements, mais au lieu de les publier ailleurs, serait-il possible de transformer cette réponse en wiki communautaire? Mes modifications sont toutes des choses qui gèrent les cas particuliers mentionnés ici, ainsi que la possibilité de choisir quel côté est redimensionné au moyen de la définition de la mise en page.
Steve Pomeroy
24

En fin de compte, j'ai généré les dimensions manuellement, ce qui fonctionne très bien:

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
la source
J'ai cherché ce type de solution pour résoudre mon problème.Avec l'aide de ce calcul, je l'ai réussi à charger l'image sans réduire le rapport hauteur / largeur.
Steve
21

Je viens de lire le code source pour ImageViewet il est fondamentalement impossible sans utiliser les solutions de sous-classification dans ce fil. Dans ImageView.onMeasurenous arrivons à ces lignes:

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

het wsont les dimensions de l'image, et p*est le remplissage.

Puis:

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

Donc, si vous avez un layout_height="wrap_content"il va définir widthSize = w + pleft + pright, ou en d'autres termes, la largeur maximale est égale à la largeur de l'image.

Cela signifie qu'à moins de définir une taille exacte, les images ne sont JAMAIS agrandies . Je considère cela comme un bogue, mais bonne chance pour que Google le remarque ou le répare. Edit: Manger mes propres mots, j'ai soumis un rapport de bogue et ils disent qu'il a été corrigé dans une prochaine version!

Une autre solution

Voici une autre solution de contournement sous-classée, mais vous devriez (en théorie, je ne l'ai pas vraiment beaucoup testé!) Pouvoir l'utiliser n'importe où ImageView. Pour l'utiliser, définissez layout_width="match_parent"et layout_height="wrap_content". C'est aussi beaucoup plus général que la solution acceptée. Par exemple, vous pouvez adapter à la hauteur et à la largeur.

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
la source
J'ai soumis un rapport de bogue . Regardez, car il est ignoré!
Timmmm
3
Apparemment, je dois manger mes mots - ils disent que cela a été corrigé dans une prochaine version!
Timmmm
Merci beaucoup Tim. Cela devrait être la bonne réponse finale. J'ai cherché beaucoup de temps et rien n'est meilleur que votre solution.!
tainy
que faire si je dois définir maxHeight lors de l'utilisation du StretchyImageView ci-dessus. Je n'ai trouvé aucune solution à cela.
tainy
17

Définir AdjustViewBounds sur true et utiliser un groupe de vues LinearLayout a très bien fonctionné pour moi. Pas besoin de sous-classer ou de demander des métriques de périphérique:

//NOTE: "this" is a subclass of LinearLayout
ImageView splashImageView = new ImageView(context);
splashImageView.setImageResource(R.drawable.splash);
splashImageView.setAdjustViewBounds(true);
addView(splashImageView);
Matt Stoker
la source
1
... ou en utilisant la propriété XML ImageView - Android: AdjustViewBounds = "true"
Anatoliy Shuba
Parfait! C'était si facile et cela étire l'image parfaite. Comment cette réponse n'est-elle pas la bonne? La réponse avec le CustomView n'a pas fonctionné pour moi (une erreur xml étrange)
user3800924
13

Je me débat avec ce problème sous une forme ou une autre depuis AGES, merci, merci, MERCI .... :)

Je voulais juste souligner que vous pouvez obtenir une solution généralisable à partir de ce que Bob Lee a fait en étendant simplement View et en remplaçant onMeasure. De cette façon, vous pouvez l'utiliser avec n'importe quel dessin de votre choix, et il ne se cassera pas s'il n'y a pas d'image:

    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
la source
2
De toutes les très nombreuses solutions à ce problème, c'est la première qui n'est qu'une solution instantanée, où les autres ont toujours un inconvénient (comme ne pas pouvoir changer l'image au moment de l'exécution sans réécriture du code). Je vous remercie!
MacD
C'est purement génial - j'ai du mal avec cela depuis un certain temps maintenant et toutes les solutions évidentes utilisant les attributs xml n'ont jamais fonctionné. Avec cela, je pourrais simplement remplacer mon ImageView par la vue étendue et tout a fonctionné comme prévu!
slott
C'est absolument la meilleure solution à ce problème.
erlando
C'est une excellente réponse. Vous pouvez utiliser cette classe dans un fichier xml sans soucis comme ceci: <com.yourpackagename.CardImageView android: layout_width = "fill_parent" android: layout_height = "wrap_content" android: background = "@ drawable / your_photo" />. Génial!
arniotaki
11

Dans certains cas, cette formule magique résout magnifiquement le problème.

Pour tous ceux qui ont des difficultés avec cela provenant d'une autre plate-forme, l' option "taille et forme pour s'adapter" est gérée à merveille dans Android, mais elle est difficile à trouver.

Vous voulez généralement cette combinaison:

  • largeur match parent,
  • contenu de l'enveloppe de hauteur,
  • AdjustViewBounds est activé (sic)
  • scale fitCenter
  • cropToPadding OFF (sic)

Ensuite, c'est automatique et incroyable.

Si vous êtes un développeur iOS, il est tout à fait étonnant de voir à quel point, sous Android, vous pouvez faire des "hauteurs de cellule totalement dynamiques" dans une vue de tableau ... euh, je veux dire ListView. Prendre plaisir.

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

entrez la description de l'image ici

Fattie
la source
6

J'ai réussi à y parvenir en utilisant uniquement ce code XML. Il se peut qu'éclipse ne rende pas la hauteur pour la montrer s'étendant pour s'adapter; cependant, lorsque vous l'exécutez réellement sur un périphérique, il restitue correctement et fournit le résultat souhaité. (enfin au moins pour moi)

<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
la source
Désolé cela ne fonctionne pas. Il met à l'échelle la largeur, mais centerCrop ne conserve pas le rapport hauteur / largeur.
ricosrealm
1
Après de nombreuses heures à lutter avec des classes personnalisées étendant ImageView (comme indiqué dans de nombreuses réponses et articles) et ayant une exception comme «Les classes suivantes sont introuvables», j'ai supprimé ce code. Soudain, j'ai remarqué un problème dans mon ImageView était dans l'attribut d'arrière-plan! Changé en srcet ImageView est devenu proportionnel.
CoolMind
5

Je l'ai fait avec ces valeurs dans un LinearLayout:

Scale type: fitStart
Layout gravity: fill_horizontal
Layout height: wrap_content
Layout weight: 1
Layout width: fill_parent
Holduel
la source
Pouvez-vous donner un exemple concret? Où dois-je mettre ces valeurs dans mon fichier xml?
John Smith Facultatif le
5

Tout le monde fait cela par programme, alors j'ai pensé que cette réponse conviendrait parfaitement ici. Ce code a fonctionné pour mon dans le xml. Je ne pense pas encore au ratio, mais je voulais toujours placer cette réponse si cela pouvait aider quelqu'un.

android:adjustViewBounds="true"

À votre santé..

Sindri Þór
la source
3

Une solution très simple consiste simplement à utiliser les fonctionnalités fournies par RelativeLayout.

Voici le xml qui le rend possible avec Android standard 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>

L'astuce est que vous définissez le ImageViewpour remplir l'écran mais il doit être au-dessus des autres mises en page. De cette façon, vous obtenez tout ce dont vous avez besoin.

Warpzit
la source
2

Sa simple question de réglage adjustViewBounds="true"et scaleType="fitCenter"dans le fichier XML pour ImageView!

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

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

Remarque: layout_widthest réglé surmatch_parent

PN de Kaushik
la source
1

Vous définissez ScaleType sur ScaleType.FIT_XY. Selon les javadocs , cela étendra l'image pour l'adapter à toute la zone, en modifiant le rapport hauteur / largeur si nécessaire. Cela expliquerait le comportement que vous observez.

Pour obtenir le comportement souhaité ... FIT_CENTER, FIT_START ou FIT_END sont proches, mais si l'image est plus étroite que haute, elle ne commencera pas à remplir la largeur. Vous pouvez cependant regarder comment ceux-ci sont mis en œuvre et vous devriez probablement être en mesure de déterminer comment l'ajuster à votre objectif.

Cheryl Simon
la source
2
J'avais essayé FIT_CENTER, mais pas les autres. Malheureusement, ils ne fonctionnent pas non plus, tous les deux avec setAdjustViewBounds définis sur true et false. Existe-t-il de toute façon pour obtenir la largeur en pixels de l'écran de l'appareil, ainsi que la largeur / hauteur de l'image téléchargée et définir manuellement la taille?
fredley
1

ScaleType.CENTER_CROP fera ce que vous voulez: étirer en pleine largeur et mettre à l'échelle la hauteur en conséquence. si la hauteur mise à l'échelle dépasse les limites de l'écran, l'image sera recadrée.

P.Melch
la source
1

Regardez, il existe une solution beaucoup plus simple à votre problème:

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
la source
1

Vous pouvez utiliser mon StretchableImageView en préservant le rapport hauteur / largeur (par largeur ou par hauteur) en fonction de la largeur et de la hauteur du dessinable:

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
la source
0

Pour moi, l'android: scaleType = "centerCrop" n'a pas résolu mon problème. Cela a en fait élargi davantage l'image. J'ai donc essayé avec android: scaleType = "fitXY" et cela fonctionnait très bien.

Ricardo
la source
0

Cela fonctionne bien selon mon exigence

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

Anand Savjani
la source