将图像缩放以填充ImageView的宽度并保持纵横比

238

我有一个GridViewGridView的数据是从服务器请求的。

这里是GridView中的项目布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/analysis_micon_bg"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:paddingBottom="@dimen/half_activity_vertical_margin"
    android:paddingLeft="@dimen/half_activity_horizontal_margin"
    android:paddingRight="@dimen/half_activity_horizontal_margin"
    android:paddingTop="@dimen/half_activity_vertical_margin" >

    <ImageView
        android:id="@+id/ranking_prod_pic"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:adjustViewBounds="true"
        android:contentDescription="@string/app_name"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/ranking_rank_num"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/ranking_prod_num"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/ranking_prod_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

我从服务器请求数据,获取图像URL并将图像加载到Bitmap中。

public static Bitmap loadBitmapFromInputStream(InputStream is) {
    return BitmapFactory.decodeStream(is);
}

public static Bitmap loadBitmapFromHttpUrl(String url) {
    try {
        return loadBitmapFromInputStream((InputStream) (new URL(url).getContent()));
    } catch (Exception e) {
        Log.e(TAG, e.getMessage());
        return null;
    }
}

这里是适配器中 getView(int position, View convertView, ViewGroup parent) 方法的代码。

Bitmap bitmap = BitmapUtil.loadBitmapFromHttpUrl(product.getHttpUrl());
prodImg.setImageBitmap(bitmap);

图像大小为210*210。我在Nexus 4上运行我的应用程序。图像填充了ImageView的宽度,但ImageView的高度没有按比例缩放。 ImageView未显示整个图像。

我该如何解决这个问题?

16个回答

635

不使用任何自定义类或库:

<ImageView
    android:id="@id/img"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:adjustViewBounds="true"
    android:scaleType="fitCenter" />

scaleType="fitCenter"(默认值)

  • 会将图像宽度调整为父容器允许的最大宽度,保持纵横比缩放。

scaleType="centerInside"

  • 如果src的本地宽度小于父容器宽度
    则将图像水平居中对齐
  • 如果src的本地宽度大于父容器宽度
    会将图像宽度调整为父容器允许的最大宽度,同时保持纵横比缩放。

无论使用android:src还是ImageView.setImage* 方法都不重要,关键可能是adjustViewBounds属性。


14
android:layout_height="wrap_content" android:adjustViewBounds="true" android:scaleType="fitCenter" 可以解决问题 - James Tan
@JamesTan fill_parent 作为宽度的设置只是一个好的实践,固定大小同样可行。 - TWiStErRob
@TWiStErRob 我意识到它需要 android:adjustViewBounds="true",否则它的高度不会适应。是的,宽度适应父级是完整版本。 - James Tan
9
在API 19及以上版本上效果非常好,但在API 16上则不行(已测试)。Alex Semeniuk的自定义视图在API 16上也有效。 - Ashkan Sarlak
1
@F.Mysir 哈哈,是的,对于问题来说哪个都没关系,但为了传播良好的实践,我完全同意。 - TWiStErRob
显示剩余6条评论

43

我喜欢arnefm的回答,但他犯了一个小错误(见评论),我将尝试进行更正:

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

/**
 * ImageView that keeps aspect ratio when scaled
 */
public class ScaleImageView extends ImageView {

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

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

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

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    try {
      Drawable drawable = getDrawable();
      if (drawable == null) {
        setMeasuredDimension(0, 0);
      } else {
        int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
        int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
        if (measuredHeight == 0 && measuredWidth == 0) { //Height and width set to wrap_content
          setMeasuredDimension(measuredWidth, measuredHeight);
        } else if (measuredHeight == 0) { //Height set to wrap_content
          int width = measuredWidth;
          int height = width *  drawable.getIntrinsicHeight() / drawable.getIntrinsicWidth();
          setMeasuredDimension(width, height);
        } else if (measuredWidth == 0){ //Width set to wrap_content
          int height = measuredHeight;
          int width = height * drawable.getIntrinsicWidth() / drawable.getIntrinsicHeight();
          setMeasuredDimension(width, height);
        } else { //Width and height are explicitly set (either to match_parent or to exact value)
          setMeasuredDimension(measuredWidth, measuredHeight);
        }
      }
    } catch (Exception e) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
  }

}

因此,如果将您的 ImageView 放在 ScrollView 中,它将被正确缩放且不会出现尺寸问题。

因此,如果(例如)将您的 ImageView 放置在 ScrollView 中,则其将被正确缩放,并且不会存在任何尺寸问题。

谢谢,我已经找到解决方案了,在arnefm的答案下面,是我添加的第一条评论。那里有一个链接。 - Joe

40

我曾经也遇到过类似的问题。我通过创建一个自定义 ImageView 解决了它。

public class CustomImageView extends ImageView

然后重写ImageView的onMeasure方法。我认为我做了类似于这样的事情:

    @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    try {
        Drawable drawable = getDrawable();

        if (drawable == null) {
            setMeasuredDimension(0, 0);
        } else {
            float imageSideRatio = (float)drawable.getIntrinsicWidth() / (float)drawable.getIntrinsicHeight();
            float viewSideRatio = (float)MeasureSpec.getSize(widthMeasureSpec) / (float)MeasureSpec.getSize(heightMeasureSpec);
            if (imageSideRatio >= viewSideRatio) {
                // Image is wider than the display (ratio)
                int width = MeasureSpec.getSize(widthMeasureSpec);
                int height = (int)(width / imageSideRatio);
                setMeasuredDimension(width, height);
            } else {
                // Image is taller than the display (ratio)
                int height = MeasureSpec.getSize(heightMeasureSpec);
                int width = (int)(height * imageSideRatio);
                setMeasuredDimension(width, height);
            }
        }
    } catch (Exception e) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

这将会拉伸图像以适应屏幕,同时保持其宽高比。


1
请点击此处,第一个答案。 - Joe
嗯,这个答案不准确。考虑一下你设置 android:layout_height="wrap_content" 的情况。在这种情况下,MeasureSpec.getSize(heightMeasureSpec) 将会是 0,而你的 viewSideRatio 将会得到 Infinity(不用想了,Java float 允许这样的值)。在这种情况下,你的 heightwidth 都将是 0 - Alex Semeniuk
1
为了让填充工作,我需要将(imageSideRatio >= viewSideRatio)交换为(imageSideRatio < viewSideRatio)...否则是一个好的答案。 - Marek Halmo

29
使用android:scaleType="centerCrop"

1
不完全是问题所问,但正是我所需! - nickjm
你能详细说明一下吗,@Jameson?你是指Android版本吗? - Mick
3
如果我使用centerCrop,图片的顶部和底部会被裁剪一点。 - Sreekanth Karumanaghat

10

查看图片(设置以下参数)

android:layout_width     = "match_parent"
android:layout_height    = "wrap_content"
android:scaleType        = "fitCenter"
android:adjustViewBounds = "true"

现在无论图像的大小如何,它的宽度都将与父元素匹配,高度将根据比例匹配。我已经测试过了,我100%确定。

我们想要这个 --> adjustViewBound = true

而不是这个 --> adjustViewBound = false

// Results will be: 
Image width -> stretched as match parent
Image height -> according to image width (maximum to aspect ratio)

// like the first one

9

我之前尝试了类似的方法,但在RelativeLayout中无法生效,结果让我花费了几个小时来解决。最终我使用了以下代码:

package com.example;

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

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

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

        if (d != null) {
            int width;
            int height;
            if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
                height = MeasureSpec.getSize(heightMeasureSpec);
                width = (int) Math.ceil(height * (float) d.getIntrinsicWidth() / d.getIntrinsicHeight());
            } else {
                width = MeasureSpec.getSize(widthMeasureSpec);
                height = (int) Math.ceil(width * (float) d.getIntrinsicHeight() / d.getIntrinsicWidth());
            }
            setMeasuredDimension(width, height);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
}

接着为了防止RelativeLayout忽略测量好的尺寸,我做了如下处理:

    <FrameLayout
        android:id="@+id/image_frame"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/something">

        <com.example.ScaledImageView
            android:id="@+id/image"
            android:layout_width="wrap_content"
            android:layout_height="150dp"/>
    </FrameLayout>

9

您不需要任何Java代码。您只需要执行以下操作:

<ImageView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:adjustViewBounds="true"
    android:scaleType="centerCrop" />

关键在于将宽度和高度设置为match parent。

2
centerCrop 实际上会裁剪图像的某些部分。 - Sreekanth Karumanaghat

7
如果您将图像设置为ImageView的背景,则此方法不适用,需要在src(android:src)中进行设置。

谢谢。


这个额外的小提示让我保持了理智。 - Travis Griggs

6
要创建一个宽度等于屏幕宽度、高度按照纵横比例设置的图像,请执行以下操作。
Glide.with(context).load(url).asBitmap().into(new SimpleTarget<Bitmap>() {
                    @Override
                    public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {

                        // creating the image that maintain aspect ratio with width of image is set to screenwidth.
                        int width = imageView.getMeasuredWidth();
                        int diw = resource.getWidth();
                        if (diw > 0) {
                            int height = 0;
                            height = width * resource.getHeight() / diw;
                            resource = Bitmap.createScaledBitmap(resource, width, height, false);
                        }
                                              imageView.setImageBitmap(resource);
                    }
                });

希望这能帮到您。

这是我找到的最佳答案,我搜索了2天,谢谢Fathima。 - Karthick Ramanathan

2

在ImageView中使用以下属性来保持宽高比:

android:adjustViewBounds="true"
android:scaleType="fitXY"

13
fitXY不会保持纵横比,它会将图像拉伸以完全适应布局的宽度和高度。 - Abandoned Cart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接