Android-ImageView bottomCrop而不是centerCrop

Android-ImageView bottomCrop而不是centerCrop,android,animation,imageview,Android,Animation,Imageview,我正在尝试定位ImageView,以便无论ImageView的高度有多小,图像的底部始终固定在视图的底部。然而,没有一种刻度类型似乎适合我正在尝试做的。CenterCrop已关闭,但我不希望图像居中。类似于CSS处理绝对定位的方式 原因是,我需要设置ImageView高度的动画,但要使其看起来好像“显示”了图像的上部。我假设找出裁剪图像和设置ImageView高度动画的方法是最简单的方法,但是如果有人知道更好的方法,我希望被指向正确的方向 感谢您的帮助。您是否尝试过Imageview的Scale

我正在尝试定位ImageView,以便无论ImageView的高度有多小,图像的底部始终固定在视图的底部。然而,没有一种刻度类型似乎适合我正在尝试做的。CenterCrop已关闭,但我不希望图像居中。类似于CSS处理绝对定位的方式

原因是,我需要设置ImageView高度的动画,但要使其看起来好像“显示”了图像的上部。我假设找出裁剪图像和设置ImageView高度动画的方法是最简单的方法,但是如果有人知道更好的方法,我希望被指向正确的方向


感谢您的帮助。

您是否尝试过Imageview的Scaletype,这是显示图像结尾的最佳选项

我最终将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);
}
答案很酷,让我想把它推广到支持上/下和左/右,数量可变。:)现在要进行顶部裁剪,只需调用
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);
    }
}
/**
*{@link android.widget.ImageView}支持垂直和垂直方向的定向裁剪
*水平方向,而不限于中心裁剪。自动设置{@link
*android.widget.ImageView.ScaleType}到矩阵,默认为中心裁剪。
*/
公共类CropImageView扩展了android.support.v7.widget.AppCompatImageView{
专用静态最终浮动默认水平偏移=0.5f;
专用静态最终浮动默认值垂直偏移=0.5f;
私有浮动mHorizontalOffsetPercent=默认水平偏移;
私有浮动mVerticalOffsetPercent=默认垂直偏移;
公共CropImageView(上下文){
这个(上下文,空);
}
公共CropImageView(上下文上下文,@Nullable AttributeSet attrs){
这(上下文,属性,0);
}
公共CropImageView(上下文上下文,@Nullable AttributeSet attrs,@AttrRes int defStyleAttr){
super(上下文、attrs、defStyleAttr);
setScaleType(ScaleType.MATRIX);
}
@凌驾
已更改尺寸的受保护空心(整数w、整数h、整数oldw、整数oldh){
super.onSizeChanged(w,h,oldw,oldh);
applyCropOffset();
}
/**
*按指定的百分比值设置裁剪框偏移。例如,中心裁剪
*be(0.5,0.5),左上角裁剪为(0,0),下中心裁剪为(0.5,1)
*/
公共无效设置CropOffset(浮动水平偏移百分比、浮动垂直偏移百分比){
如果(mHorizontalOffsetPercent<0
||mVerticalOffsetPercent<0
||mHorizontalOffsetPercent>1
||mVerticalOffsetPercent>1){
抛出新的IllegalArgumentException(“偏移值必须是介于0.0和1.0之间的浮点值”);
}
mHorizontalOffsetPercent=水平偏移百分比;
mVerticalOffsetPercent=垂直偏移百分比;
applyCropOffset();
}
私有void applyCropOffset(){
矩阵=getImageMatrix();
浮标;
int viewWidth=getWidth()-getPaddingLeft()-getPaddingRight();
int viewHeight=getHeight()-getPaddingTop()-getPaddingBottom();
int-drawableWidth=0,drawableHeight=0;
//允许稍后在代码中通过保护我们自己来设置可绘制。
if(getDrawable()!=null){
drawableWidth=getDrawable().getIntrinsicWidth();
drawableHeight=getDrawable().getIntrinsicHeight();
}
//拿天平。
if(可绘制宽度*视图高度>可绘制高度*视图宽度){
//“可绘制”比“视图”更平坦。缩放它以填充视图高度。
//在这种情况下,此处的顶部/底部作物应相同。
比例=(浮动)视图高度/(浮动)可绘制高度;
}否则{
//Drawable比view高。缩放它以填充视图宽度。
//在这种情况下,此处的左/右裁剪应该相同。
比例=(浮动)视图宽度/(浮动)可绘制宽度;
}
浮动viewToDrawableWidth=视图宽度/比例;
浮动viewToDrawableHeight=视图高度/比例;
浮动xOffset=mHorizontalOffsetPercent*(可绘制宽度-viewToDrawableWidth);
浮动Y偏移=mVerticalOffsetPercent*(可绘制高度-viewToDrawableHeight);
//定义从中获取图像部分的矩形。
RectF drawableRect=
新RectF(
xOffset,
约夫特,
xOffset+viewToDrawableWidth,
yOffset+viewToDrawableHeight);
RectF viewRect=新的RectF(0,0,VIEWWITH,viewHeight);
matrix.SetRectRect(drawableRect、viewRect、matrix.ScaleToFit.FILL);
setImageMatrix(矩阵);
}
}
工作正常。 稍微改进一下,就可以将CustomView从.xml定制为topCrop或bottomCrop。 以下是gitHub上的完整解决方案:

基于此,我做了一些改进:

  • 创建自定义XM
    val drawableRect = when (matrixType) {
        FIT_BOTTOM -> RectF(0f, drawableHeight - offset, drawableWidth, drawableHeight)
        FIT_TOP -> RectF(0f, 0f, drawableWidth, offset)
    }
    
    <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>
    
    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" />