![](/img/trans.png)
[英]Glide - Is there a benefit of using .centerCrop() instead of android:scaleType="centerCrop" on my ImageView?
[英]Android - ImageView bottomCrop instead of centerCrop
我試圖定位一個 ImageView,這樣無論 ImageView 的高度有多小,圖像的底部總是固定在視圖的底部。 但是,似乎沒有一種比例類型適合我正在嘗試做的事情。 CenterCrop 很接近,但我不希望圖像居中。 類似於 CSS 處理絕對定位的方式。
原因是,我需要為 ImageView 的高度設置動畫,但讓它看起來好像在“揭示”圖像的上部。 我認為找出這種裁剪圖像和動畫 ImageView 高度的方法是最簡單的方法,但如果有人知道更好的方法,我很樂意指出正確的方向。
任何幫助表示贊賞。
Jpoliachik 的回答很酷,讓我想將它概括為支持頂部/底部和左側/右側,數量可變。 :) 現在到頂部裁剪,只需調用setCropOffset(0,0)
,底部裁剪setCropOffset(0,1)
,左側裁剪也是setCropOffset(0,0)
,右側裁剪setCropOffset(1,0)
。 如果您想將視口偏移一維圖像的一部分,您可以調用例如setCropOffset(0, 0.25f)
將其向下移動不可見空間的 25%,而 0.5f 會將其居中。 干杯!
/**
* {@link android.widget.ImageView} that supports directional cropping in both vertical and
* horizontal directions instead of being restricted to center-crop. Automatically sets {@link
* android.widget.ImageView.ScaleType} to MATRIX and defaults to center-crop.
*/
public class CropImageView extends android.support.v7.widget.AppCompatImageView {
private static final float DEFAULT_HORIZONTAL_OFFSET = 0.5f;
private static final float DEFAULT_VERTICAL_OFFSET = 0.5f;
private float mHorizontalOffsetPercent = DEFAULT_HORIZONTAL_OFFSET;
private float mVerticalOffsetPercent = DEFAULT_VERTICAL_OFFSET;
public CropImageView(Context context) {
this(context, null);
}
public CropImageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CropImageView(Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
setScaleType(ScaleType.MATRIX);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
applyCropOffset();
}
/**
* Sets the crop box offset by the specified percentage values. For example, a center-crop would
* be (0.5, 0.5), a top-left crop would be (0, 0), and a bottom-center crop would be (0.5, 1)
*/
public void setCropOffset(float horizontalOffsetPercent, float verticalOffsetPercent) {
if (mHorizontalOffsetPercent < 0
|| mVerticalOffsetPercent < 0
|| mHorizontalOffsetPercent > 1
|| mVerticalOffsetPercent > 1) {
throw new IllegalArgumentException("Offset values must be a float between 0.0 and 1.0");
}
mHorizontalOffsetPercent = horizontalOffsetPercent;
mVerticalOffsetPercent = verticalOffsetPercent;
applyCropOffset();
}
private void applyCropOffset() {
Matrix matrix = getImageMatrix();
float scale;
int viewWidth = getWidth() - getPaddingLeft() - getPaddingRight();
int viewHeight = getHeight() - getPaddingTop() - getPaddingBottom();
int drawableWidth = 0, drawableHeight = 0;
// Allow for setting the drawable later in code by guarding ourselves here.
if (getDrawable() != null) {
drawableWidth = getDrawable().getIntrinsicWidth();
drawableHeight = getDrawable().getIntrinsicHeight();
}
// Get the scale.
if (drawableWidth * viewHeight > drawableHeight * viewWidth) {
// Drawable is flatter than view. Scale it to fill the view height.
// A Top/Bottom crop here should be identical in this case.
scale = (float) viewHeight / (float) drawableHeight;
} else {
// Drawable is taller than view. Scale it to fill the view width.
// Left/Right crop here should be identical in this case.
scale = (float) viewWidth / (float) drawableWidth;
}
float viewToDrawableWidth = viewWidth / scale;
float viewToDrawableHeight = viewHeight / scale;
float xOffset = mHorizontalOffsetPercent * (drawableWidth - viewToDrawableWidth);
float yOffset = mVerticalOffsetPercent * (drawableHeight - viewToDrawableHeight);
// Define the rect from which to take the image portion.
RectF drawableRect =
new RectF(
xOffset,
yOffset,
xOffset + viewToDrawableWidth,
yOffset + viewToDrawableHeight);
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
matrix.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.FILL);
setImageMatrix(matrix);
}
}
我最終繼承了 ImageView 並創建了一種啟用“BottomCrop”類型圖像縮放的方法。
我通過根據視圖高度計算比例和預期圖像高度,將圖像分配給正確大小的 RectF。
public class BottomCropImage extends ImageView {
public BottomCropImage(Context context) {
super(context);
setup();
}
public BottomCropImage(Context context, AttributeSet attrs) {
super(context, attrs);
setup();
}
public BottomCropImage(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
setup();
}
private void setup() {
setScaleType(ScaleType.MATRIX);
}
@Override
protected boolean setFrame(int l, int t, int r, int b) {
Matrix matrix = getImageMatrix();
float scale;
int viewWidth = getWidth() - getPaddingLeft() - getPaddingRight();
int viewHeight = getHeight() - getPaddingTop() - getPaddingBottom();
int drawableWidth = getDrawable().getIntrinsicWidth();
int drawableHeight = getDrawable().getIntrinsicHeight();
//Get the scale
if (drawableWidth * viewHeight > drawableHeight * viewWidth) {
scale = (float) viewHeight / (float) drawableHeight;
} else {
scale = (float) viewWidth / (float) drawableWidth;
}
//Define the rect to take image portion from
RectF drawableRect = new RectF(0, drawableHeight - (viewHeight / scale), drawableWidth, drawableHeight);
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
matrix.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.FILL);
setImageMatrix(matrix);
return super.setFrame(l, t, r, b);
}
}
我使用了@Jpoliachik 代碼並且效果很好,我做了一些調整,因為有時getWidth
和getHeight
返回0
- getMeasuredWidth
和getMeasuredHeight
解決了這個問題。
@Override
protected boolean setFrame(int l, int t, int r, int b) {
if (getDrawable() == null)
return super.setFrame(l, t, r, b);
Matrix matrix = getImageMatrix();
float scale;
int viewWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
int viewHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
int drawableWidth = getDrawable().getIntrinsicWidth();
int drawableHeight = getDrawable().getIntrinsicHeight();
//Get the scale
if (drawableWidth * viewHeight > drawableHeight * viewWidth) {
scale = (float) viewHeight / (float) drawableHeight;
} else {
scale = (float) viewWidth / (float) drawableWidth;
}
//Define the rect to take image portion from
RectF drawableRect = new RectF(0, drawableHeight - (viewHeight / scale), drawableWidth, drawableHeight);
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
matrix.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.FILL);
setImageMatrix(matrix);
return super.setFrame(l, t, r, b);
}
根據qix的回答,我做了一些改進:
setCropOffset()
。 相反,您可以將app:verticalCropOffset
和app:horizontalCropOffset
到您的 XML 布局(接受分數和浮點數)。app:offsetScaleType
屬性來控制圖像的縮放方式:
crop
:與原始答案相同的行為,即縮放圖像,使圖像的兩個維度都等於或大於視圖的相應維度; 然后應用app:horizontalCropOffset
和app:verticalCropOffset
fitInside
:縮放圖像,使圖像的兩個維度等於或小於視圖的相應維度; 然后應用app:horizontalFitOffset
和app:verticalFitOffset
fitX
:縮放圖像,使其 X 維度等於視圖的 X 維度。 Y 維度被縮放以便保留比例。 如果圖像的 Y 尺寸大於視圖的尺寸,則app:verticalCropOffset
,否則app:verticalFitOffset
fitY
:縮放圖像,使其 Y 尺寸等於視圖的 Y 尺寸。 X 維度被縮放以便保留比例。 如果圖像的 X 尺寸大於視圖的尺寸,則app:horizontalCropOffset
,否則app:horizontalFitOffset
我們必須添加一個新的OffsetImageView
樣式到我們的attrs.xml
:
<declare-styleable name="OffsetImageView">
<attr name="horizontalFitOffset" format="float|fraction" />
<attr name="verticalFitOffset" format="float|fraction" />
<attr name="horizontalCropOffset" format="float|fraction" />
<attr name="verticalCropOffset" format="float|fraction" />
<attr name="offsetScaleType" format="enum">
<enum name="crop" value="0"/>
<enum name="fitInside" value="1"/>
<enum name="fitX" value="2"/>
<enum name="fitY" value="3"/>
</attr>
</declare-styleable>
OffsetImageView
代碼(添加您自己的包並導入您模塊的 R 文件):
import android.content.Context
import android.content.res.TypedArray
import android.graphics.Matrix
import android.graphics.RectF
import android.util.AttributeSet
import androidx.annotation.AttrRes
import androidx.annotation.StyleableRes
import androidx.appcompat.widget.AppCompatImageView
/**
* [android.widget.ImageView] that supports directional cropping in both vertical and
* horizontal directions instead of being restricted to center-crop. Automatically sets [ ] to MATRIX and defaults to center-crop.
*
* XML attributes (for offsets either a float or a fraction is allowed in values, e. g. 50% or 0.5):
* - app:verticalCropOffset
* - app:horizontalCropOffset
* - app:verticalFitOffset
* - app:horizontalFitOffset
* - app:offsetScaleType
*
* The `app:offsetScaleType` accepts one of the enum values:
* - crop: the same behavior as in the original answer, i. e. the image is scaled so that both dimensions of the image will be equal to or larger than the corresponding dimension of the view; `app:horizontalCropOffset` and `app:verticalCropOffset` are then applied
* - fitInside: image is scaled so that both dimensions of the image will be equal to or less than the corresponding dimension of the view; `app:horizontalFitOffset` and `app:verticalFitOffset` are then applied
* - fitX: image is scaled so that its X dimension is equal to the view's X dimension. Y dimension is scaled so that the ratio is preserved. If image's Y dimension is larger than view's dimension, `app:verticalCropOffset` is applied, otherwise `app:verticalFitOffset` is applied
* - fitY: image is scaled so that its Y dimension is equal to the view's Y dimension. X dimension is scaled so that the ratio is preserved. If image's X dimension is larger than view's dimension, `app:horizontalCropOffset` is applied, otherwise `app:horizontalFitOffset` is applied
*/
class OffsetImageView(context: Context, attrs: AttributeSet?, @AttrRes defStyleAttr: Int) : AppCompatImageView(context, attrs, defStyleAttr) {
companion object {
private const val DEFAULT_HORIZONTAL_OFFSET = 0.5f
private const val DEFAULT_VERTICAL_OFFSET = 0.5f
}
enum class OffsetScaleType(val code: Int) {
CROP(0), FIT_INSIDE(1), FIT_X(2), FIT_Y(3)
}
private var mHorizontalCropOffsetPercent = DEFAULT_HORIZONTAL_OFFSET
private var mHorizontalFitOffsetPercent = DEFAULT_HORIZONTAL_OFFSET
private var mVerticalCropOffsetPercent = DEFAULT_VERTICAL_OFFSET
private var mVerticalFitOffsetPercent = DEFAULT_VERTICAL_OFFSET
private var mOffsetScaleType = OffsetScaleType.CROP
init {
scaleType = ScaleType.MATRIX
if (attrs != null) {
val a = context.obtainStyledAttributes(attrs, R.styleable.OffsetImageView, defStyleAttr, 0)
readAttrFloatValueIfSet(a, R.styleable.OffsetImageView_verticalCropOffset)?.let {
mVerticalCropOffsetPercent = it
}
readAttrFloatValueIfSet(a, R.styleable.OffsetImageView_horizontalCropOffset)?.let {
mHorizontalCropOffsetPercent = it
}
readAttrFloatValueIfSet(a, R.styleable.OffsetImageView_verticalFitOffset)?.let {
mVerticalFitOffsetPercent = it
}
readAttrFloatValueIfSet(a, R.styleable.OffsetImageView_horizontalFitOffset)?.let {
mHorizontalFitOffsetPercent = it
}
with (a) {
if (hasValue(R.styleable.OffsetImageView_offsetScaleType)) {
val code = getInt(R.styleable.OffsetImageView_offsetScaleType, -1)
if (code != -1) {
OffsetScaleType.values().find {
it.code == code
}?.let {
mOffsetScaleType = it
}
}
}
}
a.recycle()
}
}
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
applyOffset()
}
private fun readAttrFloatValueIfSet(typedArray: TypedArray, @StyleableRes index: Int): Float? {
try {
with(typedArray) {
if (!hasValue(index)) return null
var value = getFloat(index, -1f)
if (value >= 0) return value
value = getFraction(index, 1, 1, -1f)
if (value >= 0) return value
return null
}
} catch (e: RuntimeException) {
e.printStackTrace()
return null
}
}
/**
* Sets the crop box offset by the specified percentage values. For example, a center-crop would
* be (0.5, 0.5), a top-left crop would be (0, 0), and a bottom-center crop would be (0.5, 1)
*/
fun setOffsets(horizontalCropOffsetPercent: Float,
verticalCropOffsetPercent: Float,
horizontalFitOffsetPercent: Float,
verticalFitOffsetPercent: Float,
scaleType: OffsetScaleType) {
require(!(mHorizontalCropOffsetPercent < 0
|| mVerticalCropOffsetPercent < 0
|| mHorizontalFitOffsetPercent < 0
|| mVerticalFitOffsetPercent < 0
|| mHorizontalCropOffsetPercent > 1
|| mVerticalCropOffsetPercent > 1
|| mHorizontalFitOffsetPercent > 1
|| mVerticalFitOffsetPercent > 1)) { "Offset values must be a float between 0.0 and 1.0" }
mHorizontalCropOffsetPercent = horizontalCropOffsetPercent
mVerticalCropOffsetPercent = verticalCropOffsetPercent
mHorizontalFitOffsetPercent = horizontalFitOffsetPercent
mVerticalFitOffsetPercent = verticalFitOffsetPercent
mOffsetScaleType = scaleType
applyOffset()
}
private fun applyOffset() {
val matrix: Matrix = imageMatrix
val scale: Float
val viewWidth: Int = width - paddingLeft - paddingRight
val viewHeight: Int = height - paddingTop - paddingBottom
val drawable = drawable
val drawableWidth: Int
val drawableHeight: Int
if (drawable == null) {
drawableWidth = 0
drawableHeight = 0
} else {
// Allow for setting the drawable later in code by guarding ourselves here.
drawableWidth = drawable.intrinsicWidth
drawableHeight = drawable.intrinsicHeight
}
val scaleHeight = when (mOffsetScaleType) {
OffsetScaleType.CROP -> drawableWidth * viewHeight > drawableHeight * viewWidth // If drawable is flatter than view, scale it to fill the view height.
OffsetScaleType.FIT_INSIDE -> drawableWidth * viewHeight < drawableHeight * viewWidth // If drawable is is taller than view, scale according to height to fit inside.
OffsetScaleType.FIT_X -> false // User wants to fit X axis -> scale according to width
OffsetScaleType.FIT_Y -> true // User wants to fit Y axis -> scale according to height
}
// Get the scale.
scale = if (scaleHeight) {
viewHeight.toFloat() / drawableHeight.toFloat()
} else {
viewWidth.toFloat() / drawableWidth.toFloat()
}
val viewToDrawableWidth = viewWidth / scale
val viewToDrawableHeight = viewHeight / scale
if (drawableWidth >= viewToDrawableWidth && drawableHeight >= viewToDrawableHeight) {
val xOffset = mHorizontalCropOffsetPercent * (drawableWidth - viewToDrawableWidth)
val yOffset = mVerticalCropOffsetPercent * (drawableHeight - viewToDrawableHeight)
// Define the rect from which to take the image portion.
val drawableRect = RectF(
xOffset,
yOffset,
xOffset + viewToDrawableWidth,
yOffset + viewToDrawableHeight)
val viewRect = RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat())
matrix.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.FILL)
} else {
val xOffset = mHorizontalFitOffsetPercent * (viewToDrawableWidth - drawableWidth) * scale
val yOffset = mVerticalFitOffsetPercent * (viewToDrawableHeight - drawableHeight) * scale
val drawableRect = RectF(
0f,
0f,
drawableWidth.toFloat(),
drawableHeight.toFloat())
val viewRect = RectF(xOffset, yOffset, xOffset + drawableWidth * scale, yOffset + drawableHeight * scale)
matrix.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.FILL)
}
imageMatrix = matrix
}
}
在您的布局中使用如下:
<your.package.OffsetImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/image"
app:verticalFitOffset="0.3"
app:horizontalFitOffset="70%"
app:offsetScaleType="fitInside" />
此解決方案工作正常。 一點點改進將使自定義視圖從 .xml 自定義為 topCrop 或 bottomCrop。 這是 gitHub 上的完整解決方案: ScalableImageView
val drawableRect = when (matrixType) {
FIT_BOTTOM -> RectF(0f, drawableHeight - offset, drawableWidth, drawableHeight)
FIT_TOP -> RectF(0f, 0f, drawableWidth, offset)
}
您是否嘗試過 Imageview 的 Scaletype FIT_END這是顯示圖像結尾的最佳可用選項。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.