【UI篇6】我眼中的ImageView

228 阅读3分钟

“携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

ImageView是一个最基本的控件,使用的也比较多,但扪心自问时,却无法回答自己是否真的了解它,直到最近用的比较多,才决定抽空研究一下,原来涉及的东西竟然这么多,以下即为对ImageView的了解

1 概述

Displays image resources, for example Bitmap or Drawable resources. ImageView is also commonly used to apply tints to an image and handle image scaling.
在源码的开头中,介绍了ImageView的作用:显示图像资源,例如Bitmap 或 Drawable 资源。ImageView还通常用于将色调应用于图像和处理图像缩放。

下面为ImageView通用的一个示例

<LinearLayout
       xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <ImageView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:src="@drawable/my_image"
           android:contentDescription="@string/my_image_description"
           />
   </LinearLayout>

2 使用

ImageView是一个最常用的控件,使用的范围非常广泛,从美学的角度,目前为了美观,通常UI要求显示的都是带圆角的ImageView,目前常用的方式有两种:

  1. CardView
  2. 自定义View

2.1 CardView

public class CardView extends FrameLayout {

CardView 是android原生的控件,可以满足圆角的要求,但也有一个缺点,就是圆角没有那么平滑

2.2 自定义View

class RoundImageView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet?,
    defStyle: Int = 0
) : AppCompatImageView(context, attrs, defStyle) {
if (mRoundRect == null) {
    mRoundRect = RectF(0F, 0F, width.toFloat(), height.toFloat())
}
if (mHasBorder) {
    val innerPath = instance!!.getPath(
        mRoundRect!!, mBorderRadius.toFloat()
    )
    canvas.drawPath(innerPath, mBitmapPaint)
    if (isSelected) {
        val selectedOutPath = instance!!
            .getPath(
                mSelectedOuterRect!!,
                mBorderRadius - mSelectedOutlineWidth / NUMBER_FLOAT_TWO
            )
        canvas.drawPath(selectedOutPath, mSelectedOutCircle)
    }
    if (!isSelected || isSelected && mSelectedOutCircle.alpha < 255) {
        // Draw default outline if selected outline is not drawn,
        // or selected outline has alpha.
        val outPath = instance!!
            .getPath(
                mOuterRect!!,
                mBorderRadius - mDefaultOutlineWidth / NUMBER_FLOAT_TWO
            )
        canvas.drawPath(outPath, mOutCircle)
    }

通过自定义View,可以实现自己想要的圆角类型和点击动画

3 ImageView 与 Matrix

image.png 查看ImageView源码,不得不提的就是八种ScaleType,从命名可知又分为了三类:

  1. 以matrix开头,关于matrix,建议先记下矩阵位置顺序
public static final int MSCALE_X = 0;   //!< use with getValues/setValues
public static final int MSKEW_X  = 1;   //!< use with getValues/setValues
public static final int MTRANS_X = 2;   //!< use with getValues/setValues
public static final int MSKEW_Y  = 3;   //!< use with getValues/setValues
public static final int MSCALE_Y = 4;   //!< use with getValues/setValues
public static final int MTRANS_Y = 5;   //!< use with getValues/setValues
public static final int MPERSP_0 = 6;   //!< use with getValues/setValues
public static final int MPERSP_1 = 7;   //!< use with getValues/setValues
public static final int MPERSP_2 = 8;   //!< use with getValues/setValues
  1. 以Fit开头,fit就是优先满足一边,如果是fit_xy则两边都缩放到跟view一样大小,至于是否等比拉伸不管
  2. 以center开头,center顾名思义就是image在view的center
private static final ScaleType[] sScaleTypeArray = {
    ScaleType.MATRIX,
    ScaleType.FIT_XY,
    ScaleType.FIT_START,
    ScaleType.FIT_CENTER,
    ScaleType.FIT_END,
    ScaleType.CENTER,
    ScaleType.CENTER_CROP,
    ScaleType.CENTER_INSIDE
};

在源码中有下面这样一段定义,翻译过来就是:用于将图像边界缩放到此视图边界的选项,可见就是说ImageView中image的显示边界可以通过ScaleType来选择,从源码的初始化可知mScaleType的默认类型为ScaleType.FIT_CENTER


/**
 * 用于将图像边界缩放到此视图边界的选项
 * Options for scaling the bounds of an image to the bounds of this view.
 */
public enum ScaleType {
    /**
     * 绘制时使用图像矩阵进行缩放。
     * Scale using the image matrix when drawing. The image matrix can be set using
     * {@link ImageView#setImageMatrix(Matrix)}. From XML, use this syntax:
     * <code>android:scaleType="matrix"</code>.
     */
    MATRIX      (0),
    /**
     * Scale the image using {@link Matrix.ScaleToFit#FILL}.
     * From XML, use this syntax: <code>android:scaleType="fitXY"</code>.
     */
    FIT_XY      (1),
    /**
     * Scale the image using {@link Matrix.ScaleToFit#START}.
     * From XML, use this syntax: <code>android:scaleType="fitStart"</code>.
     */
    FIT_START   (2),
    /**
     * 默认类型
     * Scale the image using {@link Matrix.ScaleToFit#CENTER}.
     * From XML, use this syntax:
     * <code>android:scaleType="fitCenter"</code>.
     */
    FIT_CENTER  (3),
    /**
     * Scale the image using {@link Matrix.ScaleToFit#END}.
     * From XML, use this syntax: <code>android:scaleType="fitEnd"</code>.
     */
    FIT_END     (4),
    /**
     * Center the image in the view, but perform no scaling.
     * From XML, use this syntax: <code>android:scaleType="center"</code>.
     */
    CENTER      (5),
    /**
     * 均匀缩放图像(保持图像的纵横比),以便图像的两个尺寸(宽度和高度)将相等
     * 或大于视图的相应尺寸(减去填充)。然后图像在视图中居中。
     * Scale the image uniformly (maintain the image's aspect ratio) so
     * that both dimensions (width and height) of the image will be equal
     * to or larger than the corresponding dimension of the view
     * (minus padding). The image is then centered in the view.
     * From XML, use this syntax: <code>android:scaleType="centerCrop"</code>.
     */
    CENTER_CROP (6),
    /**
     * 均匀缩放图像(保持图像的纵横比),以便图像的两个尺寸(宽度和高度)将相等
     * 或小于视图的相应尺寸(减去填充)。然后图像在视图中居中。
     * Scale the image uniformly (maintain the image's aspect ratio) so
     * that both dimensions (width and height) of the image will be equal
     * to or less than the corresponding dimension of the view
     * (minus padding). The image is then centered in the view.
     * From XML, use this syntax: <code>android:scaleType="centerInside"</code>.
     */
    CENTER_INSIDE (7);

    ScaleType(int ni) {
        nativeInt = ni;
    }
    final int nativeInt;
}

查看调用堆栈可知,在执行setImageResource或者setImageDrawable时,会调用到configureBounds,此时会缩放image以适配view image.png

下面讲解适配过程:

  1. 获取view的宽和高,然后再判断mScaleType是哪种类型
final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
final int vheight = getHeight() - mPaddingTop - mPaddingBottom;
  1. 如果是FIT_XY则直接设置mDrawable的边界与view一致,否则就通过matrix进行转换,具体转换可以参考如下代码,总之它针对的是Drawable进行的操作
if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
    /* If the drawable has no intrinsic size, or we're told to
        scaletofit, then we just fill our entire view.
    */
    mDrawable.setBounds(0, 0, vwidth, vheight);
    mDrawMatrix = null;
} else {
    // We need to do the scaling ourself, so have the drawable
    // use its native size.
    mDrawable.setBounds(0, 0, dwidth, dheight);

    if (ScaleType.MATRIX == mScaleType) {
        // Use the specified matrix as-is.
        if (mMatrix.isIdentity()) {
            mDrawMatrix = null;
        } else {
            mDrawMatrix = mMatrix;
        }
    } else if (fits) {
        // The bitmap fits exactly, no transform needed.
        mDrawMatrix = null;
    } else if (ScaleType.CENTER == mScaleType) {
        // Center bitmap in view, no scaling.
        mDrawMatrix = mMatrix;
        mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),
                                 Math.round((vheight - dheight) * 0.5f));
    } 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(Math.round(dx), Math.round(dy));
    } else if (ScaleType.CENTER_INSIDE == mScaleType) {
        mDrawMatrix = mMatrix;
        float scale;
        float dx;
        float dy;

        if (dwidth <= vwidth && dheight <= vheight) {
            scale = 1.0f;
        } else {
            scale = Math.min((float) vwidth / (float) dwidth,
                    (float) vheight / (float) dheight);
        }

        dx = Math.round((vwidth - dwidth * scale) * 0.5f);
        dy = Math.round((vheight - dheight * scale) * 0.5f);

        mDrawMatrix.setScale(scale, scale);
        mDrawMatrix.postTranslate(dx, dy);
    } else {
        // Generate the required transform.
        mTempSrc.set(0, 0, dwidth, dheight);
        mTempDst.set(0, 0, vwidth, vheight);

        mDrawMatrix = mMatrix;
        mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
    }