Android ImageView 缩放较小的图像到宽度,具有灵活的高度,没有裁剪或失真

经常问,从不回答(至少不是以可重复的方式)。

我有一个图像视图与图像是 更小比视图。我想缩放图像到屏幕的宽度,并调整 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,像其他地方建议的那样,也没有效果。

我试过其他的组合,但都没用,所以,有人知道吗:

如何设置 ImageView 的 XML 以填充屏幕的宽度,缩放较小的图像以填充整个视图,以及在不失真或裁剪的情况下显示图像的长宽比?

编辑: 我还试着设置一个任意的数字高度。这只对 centerCrop设置有影响。它会根据视图高度使图像垂直变形。

94232 次浏览
  android:scaleType="fitCenter"

Compute a scale that will maintain the original src aspect ratio, but will also ensure that src fits entirely inside dst. At least one axis (X or Y) will fit exactly. The result is centered inside dst.

edit:

The problem here is that the layout_height="wrap_content" is not "allowing" the image to expand. You'll have to set a size for it, for that change

  android:layout_height="wrap_content"

to

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

edit2:

works fine:

<ImageView
android:id="@+id/imageView1"
android:layout_width="match_parent"
android:layout_height="300dp"
android:scaleType="fitCenter"
android:src="@drawable/img715945m" />

There is no viable solution within the XML layout standard.

The only reliable way to react to a dynamic image size is to use LayoutParams in code.

Disappointing.

I have solved this by creating a java-class that you include in your layout-file:

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

Now, you use this by added your class to your layout-file:

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

I ran into the same problem, but with a fixed height, scaled width keeping the image's original aspect ratio. I solved it via a weighted linear layout. You can hopefully modify it for your needs.

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

This is a small addition to Mark Martinsson's excellent solution.

If your image's width is larger than its height, then Mark's solution will leave space at the top and bottom of the screen.

The below fixes this by first comparing the width and height: if the image width >= height, then it will scale the height to match the screen height, and then scale the width to preserve the aspect ratio. Similarly, if the image height > width, then it will scale the width to match the screen width and then scale the height to preserve the aspect ratio.

In other words, it properly satisfies the definition of scaleType="centerCrop" :

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

Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding).

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

This solution automatically works in either portrait or landscape mode. You reference it in your layout just as you do in Mark's solution. E.g.:

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

I had the same problem, as yours. After 30 minutes of trying diffirent variations I solved the problem in this way. It gives you the flexible height, and width adjusted to the parent width

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

One more addition to Mark Matinsson's solution. I found that some of my images were over scaled than others. So I modified the class so that the image is scaled by a maximum factor. If the image is too small to be scaled at max width without becoming blurry, it stops scaling beyond the max limit.

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

Kotlin version of Mark Martinsson's answer:

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