【问题标题】:ImageView scaling TOP_CROPImageView 缩放 TOP_CROP
【发布时间】:2026-01-21 07:50:01
【问题描述】:

我有一个ImageView,它正在显示一个比设备具有更大纵横比的 png(从垂直方向上讲 - 意味着它更长)。我想在保持纵横比、匹配父级宽度并将图像视图固定到屏幕顶部的同时显示它。

使用CENTER_CROP 作为缩放类型时我遇到的问题是它(可以理解)将缩放图像居中,而不是将顶部边缘与图像视图的顶部边缘对齐。

FIT_START 的问题是图片会适合屏幕高度而不是宽度。

我已经通过使用自定义 ImageView 并覆盖 onDraw(Canvas) 并使用画布手动处理这个问题来解决这个问题;这种方法的问题在于 1)我担心可能有更简单的解决方案,2)当堆有3 mb 空闲空间(堆大小为 6 mb)并且无法找出原因。

非常欢迎任何想法/建议/解决方案:)

谢谢

附言我认为一个解决方案可能是使用矩阵比例类型并自己做,但这似乎与我当前的解决方案相同或更多!

【问题讨论】:

  • 您是否尝试过使用 CENTER_CROP 并将 ImageView 的 adjustViewBounds 属性设置为 true?
  • 是的,我已经尝试过了,恐怕没有成功,因为它会扩展视图直到它的父级宽度不大于屏幕,然后将图像在屏幕上居中多余的高度 / 2 从顶部和底部戳出

标签: android imageview


【解决方案1】:

好的,我有一个可行的解决方案。 Darko 的提示让我再次查看 ImageView 类(谢谢)并使用 Matrix 应用了转换(正如我最初怀疑的那样,但在我的第一次尝试中没有成功!)。在我的自定义 imageView 类中,我在构造函数中的super() 之后调用setScaleType(ScaleType.MATRIX),并具有以下方法。

    @Override
    protected boolean setFrame(int l, int t, int r, int b)
    {
        Matrix matrix = getImageMatrix(); 
        float scaleFactor = getWidth()/(float)getDrawable().getIntrinsicWidth();    
        matrix.setScale(scaleFactor, scaleFactor, 0, 0);
        setImageMatrix(matrix);
        return super.setFrame(l, t, r, b);
    }

我已将 int 放在 setFrame() 方法中,就像在 ImageView 中一样,对 configureBounds() 的调用在此方法中,这是所有缩放和矩阵内容发生的地方,所以对我来说似乎是合乎逻辑的(如果你不同意)

下面是来自 AOSP(Android 开源项目)的 super.setFrame() 方法

    @Override
    protected boolean setFrame(int l, int t, int r, int b) {
        boolean changed = super.setFrame(l, t, r, b);
        mHaveFrame = true;
        configureBounds();
        return changed;
    }

查找全类 src here

【讨论】:

  • 感谢代码,@doridori!它工作正常!我只是不明白你为什么在解释中重复“setFrame”方法......我只使用了第一个成功的方法(并且完全忽略了第二个 xD)
  • 在通过 xml 布局解决了这个问题两个小时后,这行得通。我希望我能给你更多的上船。
  • 我必须在 body 之前调用 super() 否则图像不会在没有重绘的情况下显示
  • @VitorHugoSchwaab 你必须使用诸如 matrix.postTranslate(..) 之类的东西
  • 不能使用背景?只能使用src?
【解决方案2】:

这是我在底部居中的代码。

顺便说一句,Dori 的代码中有一个小错误:由于在最后调用 super.frame()getWidth() 方法可能会返回错误的值。

如果您想将其置于顶部居中,只需删除 postTranslate 行即可。

好处是,您可以使用此代码将其移动到任何您想要的位置。 (对,居中 => 没问题;)

    public class CenterBottomImageView extends ImageView {
    
        public CenterBottomImageView(Context context) {
            super(context);
            setup();
        }
        
        public CenterBottomImageView(Context context, AttributeSet attrs) {
            super(context, attrs);
            setup();
        }
    
        public CenterBottomImageView(Context context, AttributeSet attrs,
                int defStyle) {
            super(context, attrs, defStyle);
            setup();
        }
        
        private void setup() {
            setScaleType(ScaleType.MATRIX);
        }
    
        @Override
        protected boolean setFrame(int frameLeft, int frameTop, int frameRight, int frameBottom) {
            if (getDrawable() == null) {
                return super.setFrame(frameLeft, frameTop, frameRight, frameBottom);
            }
            float frameWidth = frameRight - frameLeft;
            float frameHeight = frameBottom - frameTop;
            
            float originalImageWidth = (float)getDrawable().getIntrinsicWidth();
            float originalImageHeight = (float)getDrawable().getIntrinsicHeight();
            
            float usedScaleFactor = 1;
            
            if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight)) {
                // If frame is bigger than image
                // => Crop it, keep aspect ratio and position it at the bottom and center horizontally
                
                float fitHorizontallyScaleFactor = frameWidth/originalImageWidth;
                float fitVerticallyScaleFactor = frameHeight/originalImageHeight;
                
                usedScaleFactor = Math.max(fitHorizontallyScaleFactor, fitVerticallyScaleFactor);
            }
            
            float newImageWidth = originalImageWidth * usedScaleFactor;
            float newImageHeight = originalImageHeight * usedScaleFactor;
            
            Matrix matrix = getImageMatrix();
            matrix.setScale(usedScaleFactor, usedScaleFactor, 0, 0); // Replaces the old matrix completly
//comment matrix.postTranslate if you want crop from TOP
            matrix.postTranslate((frameWidth - newImageWidth) /2, frameHeight - newImageHeight);
            setImageMatrix(matrix);
            return super.setFrame(frameLeft, frameTop, frameRight, frameBottom);
        }
    
    }

初学者提示:如果它不起作用,您可能需要extends androidx.appcompat.widget.AppCompatImageView 而不是ImageView

【讨论】:

  • 太好了,感谢您指出错误。我已经有一段时间没有碰过这段代码了,所以这可能是一个愚蠢的建议,但要修复你指出的 setWidth 错误,难道不可以只使用(r-l) 吗?
  • if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight)) 行肯定应该颠倒吗?换句话说,您不应该测试图像是否大于框架吗?我建议将其替换为 if((originalImageWidth > frameWidth ) || (originalImageHeight > frameHeight ))
  • 我使用((View) getParent()).getWidth()从父级选择宽度,因为ImageViewMATCH_PARENT
  • @Jay 效果很好。我在 onLayout() 方法中进行矩阵计算,该方法在旋转时也被调用,而 setFrame 没有。
  • 感谢您,完美运行,也感谢您提供了一种通过注释 1 行代码快速将底部裁剪更改为顶部裁剪的方法
【解决方案3】:

您无需编写自定义图像视图即可获得TOP_CROP 功能。只需修改ImageViewmatrix即可。

  1. scaleType 设置为matrix 用于ImageView

    <ImageView
          android:id="@+id/imageView"
          android:contentDescription="Image"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:src="@drawable/image"
          android:scaleType="matrix"/>
    
  2. ImageView设置自定义矩阵:

    final ImageView imageView = (ImageView) findViewById(R.id.imageView);
    final Matrix matrix = imageView.getImageMatrix();
    final float imageWidth = imageView.getDrawable().getIntrinsicWidth();
    final int screenWidth = getResources().getDisplayMetrics().widthPixels;
    final float scaleRatio = screenWidth / imageWidth;
    matrix.postScale(scaleRatio, scaleRatio);
    imageView.setImageMatrix(matrix);
    

这样做将为您提供TOP_CROP 功能。

【讨论】:

  • 这对我有用。但是,如果 scaleRation scaleType 更改为 centerCrop 否则我会在边缘看到空白。
  • 如何在需要底部对齐图像的情况下使用此功能?
【解决方案4】:

此示例适用于在创建对象 + 一些优化后加载的图像。 我在代码中添加了一些 cmets 来解释发生了什么。

记得打电话:

imageView.setScaleType(ImageView.ScaleType.MATRIX);

android:scaleType="matrix"

Java 源代码:

import com.appunite.imageview.OverlayImageView;

public class TopAlignedImageView extends ImageView {
    private Matrix mMatrix;
    private boolean mHasFrame;

    @SuppressWarnings("UnusedDeclaration")
    public TopAlignedImageView(Context context) {
        this(context, null, 0);
    }

    @SuppressWarnings("UnusedDeclaration")
    public TopAlignedImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    @SuppressWarnings("UnusedDeclaration")
    public TopAlignedImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mHasFrame = false;
        mMatrix = new Matrix();
        // we have to use own matrix because:
        // ImageView.setImageMatrix(Matrix matrix) will not call
        // configureBounds(); invalidate(); because we will operate on ImageView object
    }

    @Override
    protected boolean setFrame(int l, int t, int r, int b)
    {
        boolean changed = super.setFrame(l, t, r, b);
        if (changed) {
            mHasFrame = true;
            // we do not want to call this method if nothing changed
            setupScaleMatrix(r-l, b-t);
        }
        return changed;
    }

    private void setupScaleMatrix(int width, int height) {
        if (!mHasFrame) {
            // we have to ensure that we already have frame
            // called and have width and height
            return;
        }
        final Drawable drawable = getDrawable();
        if (drawable == null) {
            // we have to check if drawable is null because
            // when not initialized at startup drawable we can
            // rise NullPointerException
            return;
        }
        Matrix matrix = mMatrix;
        final int intrinsicWidth = drawable.getIntrinsicWidth();
        final int intrinsicHeight = drawable.getIntrinsicHeight();

        float factorWidth = width/(float) intrinsicWidth;
        float factorHeight = height/(float) intrinsicHeight;
        float factor = Math.max(factorHeight, factorWidth);

        // there magic happen and can be adjusted to current
        // needs
        matrix.setTranslate(-intrinsicWidth/2.0f, 0);
        matrix.postScale(factor, factor, 0, 0);
        matrix.postTranslate(width/2.0f, 0);
        setImageMatrix(matrix);
    }

    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        // We have to recalculate image after chaning image
        setupScaleMatrix(getWidth(), getHeight());
    }

    @Override
    public void setImageResource(int resId) {
        super.setImageResource(resId);
        // We have to recalculate image after chaning image
        setupScaleMatrix(getWidth(), getHeight());
    }

    @Override
    public void setImageURI(Uri uri) {
        super.setImageURI(uri);
        // We have to recalculate image after chaning image
        setupScaleMatrix(getWidth(), getHeight());
    }

    // We do not have to overide setImageBitmap because it calls 
    // setImageDrawable method

}

【讨论】:

  • 如何在需要底部对齐图像的情况下使用此功能?
【解决方案5】:

基于 Dori,我正在使用一种解决方案,该解决方案可以根据图像的宽度或高度缩放图像以始终填充周围的容器。这允许使用图像的左上角而不是中心作为原点 (CENTER_CROP) 来缩放图像以填充整个可用空间

@Override
protected boolean setFrame(int l, int t, int r, int b)
{

    Matrix matrix = getImageMatrix(); 
    float scaleFactor, scaleFactorWidth, scaleFactorHeight;
    scaleFactorWidth = (float)width/(float)getDrawable().getIntrinsicWidth();
    scaleFactorHeight = (float)height/(float)getDrawable().getIntrinsicHeight();    

    if(scaleFactorHeight > scaleFactorWidth) {
        scaleFactor = scaleFactorHeight;
    } else {
        scaleFactor = scaleFactorWidth;
    }

    matrix.setScale(scaleFactor, scaleFactor, 0, 0);
    setImageMatrix(matrix);

    return super.setFrame(l, t, r, b);
}

我希望这会有所帮助 - 在我的项目中就像一种享受。

【讨论】:

  • 这是更好的解决方案...并添加:float width = r - l;浮动高度 = b - t;
【解决方案6】:

这些解决方案都不适合我,因为我想要一个支持从水平或垂直方向任意裁剪的类,并且我希望它允许我动态更改裁剪。我还需要Picasso 兼容性,而毕加索会懒惰地设置图像drawable。

我的实现直接改编自 AOSP 中的ImageView.java。要使用它,请在 XML 中声明如下:

    <com.yourapp.PercentageCropImageView
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="matrix"/>

来源,如果您希望获得*作物,请致电:

imageView.setCropYCenterOffsetPct(0f);

如果您希望进行底部裁剪,请致电:

imageView.setCropYCenterOffsetPct(1.0f);

如果您希望剪下 1/3,请致电:

imageView.setCropYCenterOffsetPct(0.33f);

此外,如果您选择使用其他裁剪方法,例如 fit_center,您可以这样做,并且不会触发任何自定义逻辑。 (其他实现只允许您使用他们的裁剪方法)。

最后,我添加了一个方法,redraw(),所以如果您选择在代码中动态更改您的裁剪方法/scaleType,您可以强制视图重绘。例如:

fullsizeImageView.setScaleType(ScaleType.FIT_CENTER);
fullsizeImageView.redraw();

要返回到您的自定义顶部中心三分之一裁剪,请致电:

fullsizeImageView.setScaleType(ScaleType.MATRIX);
fullsizeImageView.redraw();

这是课程:

/* 
 * Adapted from ImageView code at: 
 * http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.4_r1/android/widget/ImageView.java
 */
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;

public class PercentageCropImageView extends ImageView{

    private Float mCropYCenterOffsetPct;
    private Float mCropXCenterOffsetPct;

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

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

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

    public float getCropYCenterOffsetPct() {
        return mCropYCenterOffsetPct;
    }

    public void setCropYCenterOffsetPct(float cropYCenterOffsetPct) {
        if (cropYCenterOffsetPct > 1.0) {
            throw new IllegalArgumentException("Value too large: Must be <= 1.0");
        }
        this.mCropYCenterOffsetPct = cropYCenterOffsetPct;
    }

    public float getCropXCenterOffsetPct() {
        return mCropXCenterOffsetPct;
    }

    public void setCropXCenterOffsetPct(float cropXCenterOffsetPct) {
        if (cropXCenterOffsetPct > 1.0) {
            throw new IllegalArgumentException("Value too large: Must be <= 1.0");
        }
        this.mCropXCenterOffsetPct = cropXCenterOffsetPct;
    }

    private void myConfigureBounds() {
        if (this.getScaleType() == ScaleType.MATRIX) {
            /*
             * Taken from Android's ImageView.java implementation:
             * 
             * Excerpt from their source:
    } else if (ScaleType.CENTER_CROP == mScaleType) {
       mDrawMatrix = mMatrix;

       float scale;
       float dx = 0, dy = 0;

       if (dwidth * vheight > vwidth * dheight) {
           scale = (float) vheight / (float) dheight; 
           dx = (vwidth - dwidth * scale) * 0.5f;
       } else {
           scale = (float) vwidth / (float) dwidth;
           dy = (vheight - dheight * scale) * 0.5f;
       }

       mDrawMatrix.setScale(scale, scale);
       mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
    }
             */

            Drawable d = this.getDrawable();
            if (d != null) {
                int dwidth = d.getIntrinsicWidth();
                int dheight = d.getIntrinsicHeight();

                Matrix m = new Matrix();

                int vwidth = getWidth() - this.getPaddingLeft() - this.getPaddingRight();
                int vheight = getHeight() - this.getPaddingTop() - this.getPaddingBottom();

                float scale;
                float dx = 0, dy = 0;

                if (dwidth * vheight > vwidth * dheight) {
                    float cropXCenterOffsetPct = mCropXCenterOffsetPct != null ? 
                            mCropXCenterOffsetPct.floatValue() : 0.5f;
                    scale = (float) vheight / (float) dheight;
                    dx = (vwidth - dwidth * scale) * cropXCenterOffsetPct;
                } else {
                    float cropYCenterOffsetPct = mCropYCenterOffsetPct != null ? 
                            mCropYCenterOffsetPct.floatValue() : 0f;

                    scale = (float) vwidth / (float) dwidth;
                    dy = (vheight - dheight * scale) * cropYCenterOffsetPct;
                }

                m.setScale(scale, scale);
                m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));

                this.setImageMatrix(m);
            }
        }
    }

    // These 3 methods call configureBounds in ImageView.java class, which
    // adjusts the matrix in a call to center_crop (android's built-in 
    // scaling and centering crop method). We also want to trigger
    // in the same place, but using our own matrix, which is then set
    // directly at line 588 of ImageView.java and then copied over
    // as the draw matrix at line 942 of ImageVeiw.java
    @Override
    protected boolean setFrame(int l, int t, int r, int b) {
        boolean changed = super.setFrame(l, t, r, b);
        this.myConfigureBounds();
        return changed;
    }
    @Override
    public void setImageDrawable(Drawable d) {          
        super.setImageDrawable(d);
        this.myConfigureBounds();
    }
    @Override
    public void setImageResource(int resId) {           
        super.setImageResource(resId);
        this.myConfigureBounds();
    }

    public void redraw() {
        Drawable d = this.getDrawable();

        if (d != null) {
            // Force toggle to recalculate our bounds
            this.setImageDrawable(null);
            this.setImageDrawable(d);
        }
    }
}

【讨论】:

    【解决方案7】:

    也许进入 android 上图像视图的源代码,看看它是如何绘制中心裁剪等的。也许将其中的一些代码复制到你的方法中。我真的不知道比这样做更好的解决方案。我有手动调整和裁剪位图(搜索位图转换)的经验,这会减小其实际大小,但在此过程中仍然会产生一些开销。

    【讨论】:

      【解决方案8】:
      public class ImageViewTopCrop extends ImageView {
      public ImageViewTopCrop(Context context) {
          super(context);
          setScaleType(ScaleType.MATRIX);
      }
      
      public ImageViewTopCrop(Context context, AttributeSet attrs) {
          super(context, attrs);
          setScaleType(ScaleType.MATRIX);
      }
      
      public ImageViewTopCrop(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs, defStyle);
          setScaleType(ScaleType.MATRIX);
      }
      
      @Override
      protected boolean setFrame(int l, int t, int r, int b) {
          computMatrix();
          return super.setFrame(l, t, r, b);
      }
      
      @Override
      protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
          super.onLayout(changed, left, top, right, bottom);
          computMatrix();
      }
      
      private void computMatrix() {
          Matrix matrix = getImageMatrix();
          float scaleFactor = getWidth() / (float) getDrawable().getIntrinsicWidth();
          matrix.setScale(scaleFactor, scaleFactor, 0, 0);
          setImageMatrix(matrix);
      }
      

      }

      【讨论】:

      • computMatrix:这里可以做任何矩阵。
      • onLayout 为我节省了很多!谢谢!我遇到了一个问题,它计算矩阵但不立即显示图像并在代码上添加onLayout 解决了我的问题。
      【解决方案9】:

      如果您使用的是 Fresco (SimpleDraweeView),您可以通过以下方式轻松完成:

       PointF focusPoint = new PointF(0.5f, 0f);
       imageDraweeView.getHierarchy().setActualImageFocusPoint(focusPoint);
      

      这将是一种*作物。

      更多信息Reference Link

      【讨论】:

        【解决方案10】:

        这里的解决方案有2个问题:

        • 它们不会在 Android Studio 布局编辑器中呈现(因此您可以预览各种屏幕尺寸和纵横比)
        • 它仅按宽度缩放,因此根据设备和图像的纵横比,您最终可能会在底部看到一个空条

        这个小修改解决了问题(将代码放在 onDraw 中,并检查宽度和高度比例因子):

        @Override
        protected void onDraw(Canvas canvas) {
        
            Matrix matrix = getImageMatrix();
        
            float scaleFactorWidth = getWidth() / (float) getDrawable().getIntrinsicWidth();
            float scaleFactorHeight = getHeight() / (float) getDrawable().getIntrinsicHeight();
        
            float scaleFactor = (scaleFactorWidth > scaleFactorHeight) ? scaleFactorWidth : scaleFactorHeight;
        
            matrix.setScale(scaleFactor, scaleFactor, 0, 0);
            setImageMatrix(matrix);
        
            super.onDraw(canvas);
        }
        

        【讨论】:

          【解决方案11】:

          最简单的解决方案:裁剪图像

           @Override
              public void draw(Canvas canvas) {
                  if(getWidth() > 0){
                      int clipHeight = 250;
                      canvas.clipRect(0,clipHeight,getWidth(),getHeight());
                   }
                  super.draw(canvas);
              }
          

          【讨论】:

          • 如果图像小于视图,这将不会放大图像,这就是其他解决方案没有那么简单的原因。