Android - ImageView bottomCrop而不是centerCrop

时间:2013-09-23 05:27:13

标签: android animation imageview

我正在尝试定位ImageView,以便无论ImageView的高度有多小,图像的底部始终固定在视图的底部。但是,没有任何比例类型似乎符合我的要求。 CenterCrop很接近,但我不希望图像居中。类似于CSS处理绝对定位的方式。

原因是,我需要为ImageView的高度设置动画,但让它看起来好像是“显露”图像的上半部分。我假设找出这种裁剪图像的方法并为ImageView高度设置动画是最简单的方法,但如果有人知道更好的方法,我会喜欢被指向正确的方向。

任何帮助表示感谢。

6 个答案:

答案 0 :(得分:36)

Jpoliachik's回答很酷,让我想把它推广到顶部/底部和左/右,可变量。 :)现在回到顶部裁剪,只需拨打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);
    }
}

答案 1 :(得分:22)

我最终继承了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);
}        

}

答案 2 :(得分:13)

我使用了@Jpoliachik代码并且运行良好,我做了一些调整,因为有时候getWidthgetHeight正在返回0 - getMeasuredWidth和{{1}解决了这个问题。

getMeasuredHeight

答案 3 :(得分:5)

基于qix's answer,我进行了一些改进:

  1. 创建的自定义XML属性。您不必致电setCropOffset()。相反,您可以仅将app:verticalCropOffsetapp:horizontalCropOffset添加到XML布局中(接受小数和浮点数)。
  2. 已添加app:offsetScaleType属性来控制图像的缩放比例:
    • crop:与原始答案相同的行为,即e。缩放图像,使图像的两个维度都等于或大于视图的相应维度;然后应用app:horizontalCropOffsetapp:verticalCropOffset
    • fitInside:缩放图像,使图像的两个尺寸都等于或小于视图的相应尺寸;然后应用app:horizontalFitOffsetapp:verticalFitOffset
    • fitX:缩放图像,使其图像的X尺寸等于视图的X尺寸。缩放Y尺寸,以便保留比例。如果图像的Y尺寸大于视图的尺寸,则应用app:verticalCropOffset,否则应用app:verticalFitOffset
    • fitY:缩放图像,使其图像的Y尺寸等于视图的Y尺寸。缩放X尺寸,以便保留比例。如果图像的X尺寸大于视图的尺寸,则应用app:horizontalCropOffset,否则应用app:horizontalFitOffset
  3. 已将代码转换为Kotlin
  4. 少量的重构可以提高Kotlin的可读性

我们必须为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" />

答案 4 :(得分:0)

This solution工作正常。 稍作改进将使CustomView可从.xml定制为topCrop或bottomCrop。 这是gitHub的完整解决方案:ScalableImageView

val drawableRect = when (matrixType) {
    FIT_BOTTOM -> RectF(0f, drawableHeight - offset, drawableWidth, drawableHeight)
    FIT_TOP -> RectF(0f, 0f, drawableWidth, offset)
}

答案 5 :(得分:-3)

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