ImageView缩放TOP_CROP
我有一个ImageView
,它显示的纵横比大于设备的纵横比(垂直方向 – 意思是它的长度)。 我想在保持宽高比的同时显示这个,匹配父宽度,并将imageview固定到屏幕的顶部。
我使用CENTER_CROP
作为缩放types时CENTER_CROP
的问题是,它会(可以理解)将缩放的图像居中,而不是将图像的上边缘与图像视图alignment。
FIT_START
的问题是图像将适合屏幕高度,而不是填充宽度。
我已经解决了这个问题,通过使用自定义ImageView和覆盖onDraw(Canvas)
并手动使用canvas处理这个问题; 这个方法的问题是:1)我担心可能有一个更简单的解决scheme,2)当我试图设置一个src图像时,在构造函数中的super(AttributeSet)
有3 MB免费(堆大小为6 MB),无法解决原因。
任何想法/build议/解决scheme都非常欢迎:)
谢谢
PS我认为一个解决scheme可能是使用matrix规模types,并自己做,但似乎是相同或更多的工作比我目前的解决scheme!
好的,我有一个工作解决scheme。 从Darko的提示让我再次看ImageView类(谢谢),并应用matrix转换(如我最初怀疑,但没有在我的第一次尝试成功!)。 在我自定义的setScaleType(ScaleType.MATRIX)
类中,我在构造函数中的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()
在这个方法中,这是所有缩放和matrix的东西发生的地方,所以对我来说似乎是合乎逻辑的(比如说,如果你不同意)
以下是AOSP的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; }
在这里find完整的类src
这里是我的代码,它在底部居中。 顺便说一句。 在Dori的代码中是一个小错误:由于super.frame()
被调用的最后, getWidth()
方法可能会返回错误的值。 如果你想在顶部居中,只需删除postTranslate行,就完成了。 好的是,用这个代码你可以把它移动到任何你想要的地方。 (right,center =>没问题;)
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) { 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); } }
此示例适用于在创build对象+某些优化之后加载的图像。 我在代码中添加了一些注释来解释发生了什么。
记得致电:
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(rl, bt); } 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 }
您不需要编写自定义图像视图来获取TOP_CROP
function。 你只需要修改ImageView
的matrix
。
-
将
scaleType
设置为ImageView
matrix
:<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"/>
-
为
ImageView
设置一个自定义matrix: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
function。
基于Dori我使用的是一种解决scheme,根据图像的宽度或高度来缩放图像,以便始终填充周围的容器。 这允许缩放图像以使用图像的左上点而不是中心作为原点(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); }
我希望这有助于 – 在我的项目中像一个享受。
这些解决scheme都不适合我,因为我想要一个从水平或垂直方向支持任意裁剪的类,我希望它能够dynamic地更改裁剪。 我也需要毕加索的兼容性,毕加索懒洋洋地设置图像绘制。
我的实现直接从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);
此外,如果您select使用其他裁剪方法,比如fit_center,则可以这样做,并且不会触发此自定义逻辑。 (其他实现只能让你使用他们的裁剪方法)。
最后,我添加了一个方法redraw(),所以如果您select在代码中dynamic更改裁剪方法/ scaleType,则可以强制重绘视图。 例如:
fullsizeImageView.setScaleType(ScaleType.FIT_CENTER); fullsizeImageView.redraw();
要返回到您的自定义顶级中心第三作物,请致电:
fullsizeImageView.setScaleType(ScaleType.MATRIX); fullsizeImageView.redraw();
这是class级:
/* * 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); } } }
也许进入android的图像视图的源代码,看看它如何绘制中心作物等。也许复制一些代码到您的方法。 我不知道有一个比这个更好的解决scheme。 我有手动resize和裁剪位图(search位图转换)的经验,这会减less其实际大小,但它仍然会在此过程中创build一些开销。
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); }
}
这里的解决scheme有两个问题:
- 它们不会在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); }
如果你正在使用Fresco(SimpleDraweeView),你可以很容易地做到这一点:
PointF focusPoint = new PointF(0.5f, 0f); imageDraweeView.getHierarchy().setActualImageFocusPoint(focusPoint);
这将是一个顶级作物。
参考链接的更多信息
最简单的解决scheme:剪切图像
@Override public void draw(Canvas canvas) { if(getWidth() > 0){ int clipHeight = 250; canvas.clipRect(0,clipHeight,getWidth(),getHeight()); } super.draw(canvas); }