揭秘 Android ImageView:从源码深度剖析使用原理

揭秘 Android ImageView:从源码深度剖析使用原理

一、引言

在 Android 开发的世界里,ImageView 是一个极其常用的组件,它就像是一个神奇的窗口,能将各种图片资源呈现在用户眼前。无论是展示产品图片、用户头像,还是作为界面的背景装饰,ImageView 都发挥着重要作用。然而,大多数开发者只是停留在简单使用 ImageView 的层面,对于其背后的工作原理知之甚少。本文将带领大家深入 ImageView 的源码,一步一步揭开它的神秘面纱,让你对 ImageView 的使用有一个全新的认识。

二、ImageView 概述

2.1 基本概念

ImageView 是 Android 系统中用于显示图片的视图类,它继承自 View 类,属于 android.widget 包。ImageView 可以加载本地资源、网络图片,还能对图片进行缩放、裁剪、旋转等操作,以满足不同的布局和显示需求。

2.2 常用属性

  • src:用于设置 ImageView 显示的图片资源,可以是本地的 Drawable 资源,也可以是网络图片的 URL。
  • scaleType:用于设置图片的缩放类型,常见的有 CENTERCENTER_CROPCENTER_INSIDEFIT_XY 等。
  • adjustViewBounds:用于设置是否根据图片的宽高比例调整 ImageView 的大小。

三、ImageView 的构造函数

3.1 源码分析

java 复制代码
// 构造函数,用于在代码中创建 ImageView 实例
public ImageView(Context context) {
    // 调用父类 View 的构造函数,传入上下文
    super(context); 
    // 初始化 ImageView 的属性
    initImageView(); 
}

// 构造函数,用于在 XML 布局文件中创建 ImageView 实例
public ImageView(Context context, @Nullable AttributeSet attrs) {
    // 调用父类 View 的构造函数,传入上下文和属性集,默认样式为 0
    this(context, attrs, 0); 
}

// 构造函数,用于在 XML 布局文件中创建 ImageView 实例,并指定默认样式
public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    // 调用父类 View 的构造函数,传入上下文、属性集、默认样式和默认资源
    this(context, attrs, defStyleAttr, 0); 
}

// 构造函数,用于在 XML 布局文件中创建 ImageView 实例,并指定默认样式和默认资源
public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    // 调用父类 View 的构造函数
    super(context, attrs, defStyleAttr, defStyleRes); 
    // 解析 XML 布局文件中的属性
    final TypedArray a = context.obtainStyledAttributes(
            attrs, com.android.internal.R.styleable.ImageView, defStyleAttr, defStyleRes);
    // 获取 src 属性的值
    Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);
    if (d != null) {
        // 如果 src 属性有值,设置 ImageView 的图片资源
        setImageDrawable(d); 
    }
    // 获取 scaleType 属性的值
    int index = a.getInt(com.android.internal.R.styleable.ImageView_scaleType, -1);
    if (index >= 0) {
        // 如果 scaleType 属性有值,设置 ImageView 的缩放类型
        setScaleType(ScaleType.values()[index]); 
    }
    // 获取 adjustViewBounds 属性的值
    boolean adjustViewBounds = a.getBoolean(
            com.android.internal.R.styleable.ImageView_adjustViewBounds, false);
    if (adjustViewBounds) {
        // 如果 adjustViewBounds 属性为 true,设置 ImageView 调整边界
        setAdjustViewBounds(true); 
    }
    // 回收 TypedArray 对象
    a.recycle(); 
    // 初始化 ImageView 的属性
    initImageView(); 
}

3.2 详细解释

  • 第一个构造函数 ImageView(Context context):用于在代码中直接创建 ImageView 实例,调用父类 View 的构造函数并初始化 ImageView 的属性。
  • 第二个构造函数 ImageView(Context context, @Nullable AttributeSet attrs):用于在 XML 布局文件中创建 ImageView 实例,调用第三个构造函数并传入默认样式为 0。
  • 第三个构造函数 ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr):用于在 XML 布局文件中创建 ImageView 实例,并指定默认样式,调用第四个构造函数并传入默认资源为 0。
  • 第四个构造函数 ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes):用于在 XML 布局文件中创建 ImageView 实例,并指定默认样式和默认资源。该构造函数会解析 XML 布局文件中的属性,如 srcscaleTypeadjustViewBounds 等,并根据这些属性设置 ImageView 的相应属性,最后调用 initImageView() 方法初始化 ImageView 的属性。

四、ImageView 的图片加载

4.1 setImageDrawable 方法

java 复制代码
// 设置 ImageView 显示的图片资源为 Drawable 对象
public void setImageDrawable(@Nullable Drawable drawable) {
    // 如果传入的 Drawable 对象与当前的 Drawable 对象相同,直接返回
    if (mDrawable == drawable) {
        return; 
    }
    // 记录旧的 Drawable 对象
    final int oldWidth = mDrawableWidth; 
    final int oldHeight = mDrawableHeight;
    // 清空旧的 Drawable 对象
    updateDrawable(null); 
    // 设置新的 Drawable 对象
    mDrawable = drawable; 
    if (drawable != null) {
        // 如果新的 Drawable 对象不为空,设置其回调为当前 ImageView
        drawable.setCallback(this); 
        // 如果 Drawable 对象是可缩放的,设置其过滤属性
        if (drawable.isStateful()) {
            drawable.setState(getDrawableState()); 
        }
        // 计算 Drawable 对象的最小宽度和高度
        drawable.setVisible(getVisibility() == VISIBLE, true); 
        mDrawableWidth = drawable.getIntrinsicWidth(); 
        mDrawableHeight = drawable.getIntrinsicHeight(); 
        // 应用缩放类型
        applyImageMatrix(); 
        // 检查是否需要调整 ImageView 的边界
        configureBounds(); 
    } else {
        // 如果新的 Drawable 对象为空,重置宽度和高度
        mDrawableWidth = mDrawableHeight = -1; 
    }
    // 重新请求布局
    requestLayout(); 
    // 使 ImageView 无效,触发重绘
    invalidate(); 
}

4.2 详细解释

  • 首先检查传入的 Drawable 对象是否与当前的 Drawable 对象相同,如果相同则直接返回,避免不必要的操作。
  • 记录旧的 Drawable 对象的宽度和高度,然后清空旧的 Drawable 对象。
  • 设置新的 Drawable 对象,并为其设置回调为当前 ImageView,以便在 Drawable 对象状态改变时通知 ImageView
  • 如果 Drawable 对象是可缩放的,设置其状态为当前 ImageView 的状态。
  • 计算 Drawable 对象的最小宽度和高度,并应用缩放类型。
  • 检查是否需要调整 ImageView 的边界。
  • 如果新的 Drawable 对象为空,重置宽度和高度。
  • 最后,重新请求布局并使 ImageView 无效,触发重绘。

4.3 setImageResource 方法

java 复制代码
// 设置 ImageView 显示的图片资源为资源 ID
public void setImageResource(@DrawableRes int resId) {
    // 如果资源 ID 为 0,设置 Drawable 对象为空
    if (resId == 0) {
        setImageDrawable(null); 
    } else {
        // 从资源中获取 Drawable 对象
        final Drawable d = mContext.getDrawable(resId); 
        // 设置 ImageView 显示的图片资源为获取到的 Drawable 对象
        setImageDrawable(d); 
    }
}

4.4 详细解释

  • 如果传入的资源 ID 为 0,调用 setImageDrawable(null) 方法将 ImageViewDrawable 对象设置为空。
  • 否则,从资源中获取 Drawable 对象,并调用 setImageDrawable 方法设置 ImageView 显示的图片资源为获取到的 Drawable 对象。

4.5 setImageURI 方法

java 复制代码
// 设置 ImageView 显示的图片资源为 URI
public void setImageURI(@Nullable Uri uri) {
    // 记录当前的 URI
    mUri = uri; 
    // 如果 URI 不为空,设置 ImageView 显示的图片资源为从 URI 加载的 Drawable 对象
    if (mUri != null) {
        setImageDrawable(loadDrawableFromUri(mUri)); 
    } else {
        // 如果 URI 为空,设置 ImageView 显示的图片资源为空
        setImageDrawable(null); 
    }
}

// 从 URI 加载 Drawable 对象
private Drawable loadDrawableFromUri(Uri uri) {
    try {
        // 获取 ContentResolver 对象
        ContentResolver cr = mContext.getContentResolver(); 
        // 打开 URI 对应的输入流
        InputStream is = cr.openInputStream(uri); 
        if (is != null) {
            // 如果输入流不为空,从输入流中解码 Drawable 对象
            Drawable drawable = Drawable.createFromStream(is, uri.toString()); 
            // 关闭输入流
            is.close(); 
            return drawable; 
        }
    } catch (FileNotFoundException e) {
        // 处理文件未找到异常
        Log.e(TAG, "Unable to open content: " + uri, e); 
    } catch (IOException e) {
        // 处理输入输出异常
        Log.e(TAG, "Got exception while processing stream for " + uri, e); 
    }
    return null; 
}

4.6 详细解释

  • setImageURI 方法用于设置 ImageView 显示的图片资源为 URI。首先记录当前的 URI,如果 URI 不为空,调用 loadDrawableFromUri 方法从 URI 加载 Drawable 对象并设置给 ImageView;如果 URI 为空,设置 ImageView 显示的图片资源为空。
  • loadDrawableFromUri 方法用于从 URI 加载 Drawable 对象。它通过 ContentResolver 打开 URI 对应的输入流,然后从输入流中解码 Drawable 对象。如果加载过程中出现异常,会记录错误日志并返回 null

五、ImageView 的缩放类型

5.1 ScaleType 枚举

java 复制代码
// 定义 ImageView 的缩放类型枚举
public enum ScaleType {
    // 居中显示,不进行缩放
    CENTER, 
    // 居中裁剪,使图片填满 ImageView
    CENTER_CROP, 
    // 居中显示,使图片完全显示在 ImageView 内
    CENTER_INSIDE, 
    // 拉伸图片,使图片的宽高与 ImageView 的宽高匹配
    FIT_XY, 
    // 等比例缩放图片,使图片的宽或高与 ImageView 的宽或高匹配,图片完全显示在 ImageView 内
    FIT_START, 
    // 等比例缩放图片,使图片的宽或高与 ImageView 的宽或高匹配,图片完全显示在 ImageView 内,居中显示
    FIT_CENTER, 
    // 等比例缩放图片,使图片的宽或高与 ImageView 的宽或高匹配,图片完全显示在 ImageView 内,居右或居下显示
    FIT_END, 
    // 使用矩阵进行缩放
    MATRIX 
}

5.2 详细解释

  • CENTER:将图片居中显示,不进行任何缩放操作。
  • CENTER_CROP:将图片居中裁剪,使图片的宽高比与 ImageView 的宽高比相同,并且图片填满 ImageView
  • CENTER_INSIDE:将图片居中显示,等比例缩放图片,使图片的宽和高都不超过 ImageView 的宽和高,图片完全显示在 ImageView 内。
  • FIT_XY:将图片拉伸或压缩,使图片的宽高与 ImageView 的宽高完全匹配,可能会导致图片变形。
  • FIT_START:等比例缩放图片,使图片的宽或高与 ImageView 的宽或高匹配,图片完全显示在 ImageView 内,并且图片的左上角与 ImageView 的左上角对齐。
  • FIT_CENTER:等比例缩放图片,使图片的宽或高与 ImageView 的宽或高匹配,图片完全显示在 ImageView 内,并且图片居中显示。
  • FIT_END:等比例缩放图片,使图片的宽或高与 ImageView 的宽或高匹配,图片完全显示在 ImageView 内,并且图片的右下角与 ImageView 的右下角对齐。
  • MATRIX:使用矩阵进行缩放,开发者可以自定义矩阵来实现更复杂的缩放效果。

5.3 applyImageMatrix 方法

java 复制代码
// 应用缩放类型
private void applyImageMatrix() {
    // 获取当前的缩放类型
    final ScaleType scaleType = mScaleType; 
    if (scaleType == ScaleType.MATRIX) {
        // 如果缩放类型为 MATRIX,直接使用自定义矩阵
        if (mMatrix.isIdentity()) {
            // 如果矩阵为单位矩阵,设置 ImageView 的矩阵为 null
            setImageMatrix(null); 
        } else {
            // 否则,设置 ImageView 的矩阵为自定义矩阵
            setImageMatrix(mMatrix); 
        }
    } else {
        // 如果缩放类型不为 MATRIX,根据缩放类型计算矩阵
        final Matrix matrix = mDrawMatrix; 
        if (mDrawable == null) {
            // 如果 Drawable 对象为空,设置矩阵为 null
            matrix.reset(); 
            setImageMatrix(null); 
            return; 
        }
        // 获取 Drawable 对象的宽度和高度
        final int dwidth = mDrawableWidth; 
        final int dheight = mDrawableHeight;
        // 获取 ImageView 的宽度和高度
        final int vwidth = getWidth() - mPaddingLeft - mPaddingRight; 
        final int vheight = getHeight() - mPaddingTop - mPaddingBottom;
        // 判断是否需要调整 ImageView 的边界
        final boolean fits = (dwidth < 0 || vwidth == dwidth) &&
                (dheight < 0 || vheight == dheight);
        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == scaleType) {
            // 如果 Drawable 对象的宽度或高度小于等于 0,或者缩放类型为 FIT_XY,拉伸图片
            matrix.setScale((float) vwidth / dwidth, (float) vheight / dheight);
            fits = true; 
        } else {
            // 根据不同的缩放类型计算矩阵
            if (ScaleType.CENTER == scaleType) {
                // 居中显示,不进行缩放
                matrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f),
                        (int) ((vheight - dheight) * 0.5f + 0.5f));
            } else if (ScaleType.CENTER_CROP == scaleType) {
                // 居中裁剪,使图片填满 ImageView
                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;
                }
                matrix.setScale(scale, scale);
                matrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
            } else if (ScaleType.CENTER_INSIDE == scaleType) {
                // 居中显示,使图片完全显示在 ImageView 内
                float scale; 
                if (dwidth <= vwidth && dheight <= vheight) {
                    scale = 1.0f;
                } else {
                    scale = Math.min((float) vwidth / (float) dwidth,
                            (float) vheight / (float) dheight);
                }
                float dx = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);
                float dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);
                matrix.setScale(scale, scale);
                matrix.postTranslate(dx, dy);
            } else {
                // 其他缩放类型,使用 ImageView 提供的计算方法
                matrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(scaleType));
                if (!fits) {
                    matrix.postTranslate((int) (matrix.getTranslateX() + 0.5f),
                            (int) (matrix.getTranslateY() + 0.5f));
                }
            }
        }
        // 设置 ImageView 的矩阵
        setImageMatrix(matrix); 
    }
}

5.4 详细解释

  • 首先获取当前的缩放类型,如果缩放类型为 MATRIX,直接使用自定义矩阵设置给 ImageView
  • 如果缩放类型不为 MATRIX,根据不同的缩放类型计算矩阵。
  • 对于 FIT_XY 缩放类型,直接拉伸图片使图片的宽高与 ImageView 的宽高匹配。
  • 对于其他缩放类型,根据 Drawable 对象的宽度和高度、ImageView 的宽度和高度以及缩放类型的特点,计算矩阵的缩放比例和平移量。
  • 最后,将计算得到的矩阵设置给 ImageView

六、ImageView 的布局测量

6.1 onMeasure 方法

java 复制代码
// 测量 ImageView 的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 调用父类的 onMeasure 方法进行初步测量
    super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
    // 获取测量模式和大小
    final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 
    final int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 
    final int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
    // 获取 Drawable 对象
    final Drawable d = getDrawable(); 
    if (d == null) {
        // 如果 Drawable 对象为空,设置 ImageView 的大小为测量的大小
        setMeasuredDimension(widthSpecSize, heightSpecSize); 
    } else {
        // 如果 Drawable 对象不为空,根据 Drawable 对象的大小和缩放类型计算 ImageView 的大小
        int intrinsicWidth = d.getIntrinsicWidth(); 
        int intrinsicHeight = d.getIntrinsicHeight();
        int width; 
        int height;
        if (mAdjustViewBounds) {
            // 如果需要调整 ImageView 的边界
            if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
                // 如果宽度和高度的测量模式都是精确模式,使用测量的大小
                width = widthSpecSize; 
                height = heightSpecSize;
            } else if (widthSpecMode == MeasureSpec.EXACTLY) {
                // 如果宽度的测量模式是精确模式,根据宽度和 Drawable 对象的宽高比计算高度
                width = widthSpecSize; 
                height = (int) (width * (float) intrinsicHeight / intrinsicWidth);
                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
                    // 如果高度的测量模式是最大模式,并且计算得到的高度超过了最大高度,使用最大高度
                    height = heightSpecSize; 
                }
            } else if (heightSpecMode == MeasureSpec.EXACTLY) {
                // 如果高度的测量模式是精确模式,根据高度和 Drawable 对象的宽高比计算宽度
                height = heightSpecSize; 
                width = (int) (height * (float) intrinsicWidth / intrinsicHeight);
                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
                    // 如果宽度的测量模式是最大模式,并且计算得到的宽度超过了最大宽度,使用最大宽度
                    width = widthSpecSize; 
                }
            } else {
                // 如果宽度和高度的测量模式都不是精确模式,根据 Drawable 对象的大小和最大模式的限制计算宽度和高度
                width = intrinsicWidth; 
                height = intrinsicHeight;
                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
                    width = widthSpecSize; 
                    height = (int) (width * (float) intrinsicHeight / intrinsicWidth);
                }
                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
                    height = heightSpecSize; 
                    width = (int) (height * (float) intrinsicWidth / intrinsicHeight);
                }
            }
        } else {
            // 如果不需要调整 ImageView 的边界,根据缩放类型计算宽度和高度
            if (widthSpecMode == MeasureSpec.EXACTLY) {
                width = widthSpecSize; 
            } else {
                width = intrinsicWidth; 
                if (widthSpecMode == MeasureSpec.AT_MOST) {
                    width = Math.min(width, widthSpecSize); 
                }
            }
            if (heightSpecMode == MeasureSpec.EXACTLY) {
                height = heightSpecSize; 
            } else {
                height = intrinsicHeight; 
                if (heightSpecMode == MeasureSpec.AT_MOST) {
                    height = Math.min(height, heightSpecSize); 
                }
            }
        }
        // 设置 ImageView 的测量大小
        setMeasuredDimension(width + getPaddingLeft() + getPaddingRight(),
                height + getPaddingTop() + getPaddingBottom()); 
    }
}

6.2 详细解释

  • 首先调用父类的 onMeasure 方法进行初步测量。
  • 获取宽度和高度的测量模式和大小。
  • 获取 Drawable 对象,如果 Drawable 对象为空,直接设置 ImageView 的大小为测量的大小。
  • 如果 Drawable 对象不为空,根据 mAdjustViewBounds 属性的值进行不同的处理:
    • 如果 mAdjustViewBoundstrue,根据宽度和高度的测量模式以及 Drawable 对象的宽高比计算 ImageView 的宽度和高度。
    • 如果 mAdjustViewBoundsfalse,根据宽度和高度的测量模式以及 Drawable 对象的大小计算 ImageView 的宽度和高度。
  • 最后,设置 ImageView 的测量大小,包括内边距。

七、ImageView 的绘制

7.1 onDraw 方法

java 复制代码
// 绘制 ImageView
@Override
protected void onDraw(Canvas canvas) {
    // 调用父类的 onDraw 方法
    super.onDraw(canvas); 
    // 获取 Drawable 对象
    final Drawable drawable = mDrawable; 
    if (drawable == null) {
        // 如果 Drawable 对象为空,直接返回
        return; 
    }
    if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
        // 如果矩阵为空,并且内边距为 0,直接绘制 Drawable 对象
        drawable.draw(canvas); 
    } else {
        // 如果矩阵不为空,或者内边距不为 0,保存画布状态
        final int saveCount = canvas.getSaveCount(); 
        canvas.save();
        if (mCropToPadding) {
            // 如果需要裁剪到内边距,裁剪画布
            final int scrollX = mScrollX; 
            final int scrollY = mScrollY;
            canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
                    scrollX + getWidth() - mPaddingRight, scrollY + getHeight() - mPaddingBottom);
        }
        // 移动画布到内边距的位置
        canvas.translate(mPaddingLeft, mPaddingTop); 
        if (mDrawMatrix != null) {
            // 如果矩阵不为空,应用矩阵到画布
            canvas.concat(mDrawMatrix); 
        }
        // 绘制 Drawable 对象
        drawable.draw(canvas); 
        // 恢复画布状态
        canvas.restoreToCount(saveCount); 
    }
}

7.2 详细解释

  • 首先调用父类的 onDraw 方法。
  • 获取 Drawable 对象,如果 Drawable 对象为空,直接返回。
  • 如果矩阵为空,并且内边距为 0,直接绘制 Drawable 对象。
  • 否则,保存画布状态,根据 mCropToPadding 属性的值决定是否裁剪画布。
  • 移动画布到内边距的位置,如果矩阵不为空,应用矩阵到画布。
  • 绘制 Drawable 对象,最后恢复画布状态。

八、ImageView 的事件处理

8.1 触摸事件处理

java 复制代码
// 处理触摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
    // 如果 ImageView 不可用,直接返回 false
    if (!isEnabled()) {
        return false; 
    }
    // 获取触摸事件的动作
    final int action = event.getAction(); 
    switch (action) {
        case MotionEvent.ACTION_DOWN:
            // 手指按下事件
            // 设置按下状态
            setPressed(true); 
            // 调用回调方法
            if (mOnClickListener != null) {
                mHasPerformedLongPress = false;
                postDelayed(mCheckForLongPress, ViewConfiguration.getLongPressTimeout());
            }
            break;
        case MotionEvent.ACTION_MOVE:
            // 手指移动事件
            // 检查手指是否移出 ImageView 的范围
            final int x = (int) event.getX(); 
            final int y = (int) event.getY();
            final int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
            if ((x < -slop || x >= getWidth() + slop || y < -slop || y >= getHeight() + slop)
                    && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                // 如果移出范围,取消按下状态
                setPressed(false); 
                removeCallbacks(mCheckForLongPress);
            }
            break;
        case MotionEvent.ACTION_UP:
            // 手指抬起事件
            // 检查是否执行了长按操作
            boolean hadTapDown = (mPrivateFlags & PFLAG_PRESSED) != 0; 
            removeCallbacks(mCheckForLongPress);
            if (hadTapDown) {
                // 如果按下状态为 true,取消按下状态
                setPressed(false); 
                if (!mHasPerformedLongPress) {
                    // 如果没有执行长按操作,调用点击监听器
                    if (mOnClickListener != null) {
                        mOnClickListener.onClick(this); 
                    }
                }
            }
            break;
        case MotionEvent.ACTION_CANCEL:
            // 触摸事件取消
            // 取消按下状态
            setPressed(false); 
            removeCallbacks(mCheckForLongPress);
            break;
    }
    return true; 
}

8.2 详细解释

  • 首先检查 ImageView 是否可用,如果不可用,直接返回 false
  • 根据触摸事件的动作进行不同的处理:
    • ACTION_DOWN:手指按下事件,设置 ImageView 为按下状态,并启动长按检测。
    • ACTION_MOVE:手指移动事件,检查手指是否移出 ImageView 的范围,如果移出范围,取消按下状态并停止长按检测。
    • ACTION_UP:手指抬起事件,检查是否执行了长按操作,如果没有执行长按操作,调用点击监听器。
    • ACTION_CANCEL:触摸事件取消,取消按下状态并停止长按检测。
  • 最后返回 true,表示事件已经被处理。

九、ImageView 的状态保存与恢复

9.1 onSaveInstanceState 方法

java 复制代码
// 保存 ImageView 的状态
@Override
public Parcelable onSaveInstanceState() {
    // 调用父类的 onSaveInstanceState 方法保存父类的状态
    Parcelable superState = super.onSaveInstanceState(); 
    // 创建 SavedState 对象
    SavedState ss = new SavedState(superState); 
    // 保存 ImageView 的 URI
    ss.uri = mUri; 
    return ss; 
}

9.2 详细解释

  • 首先调用父类的 onSaveInstanceState 方法保存父类的状态。
  • 创建 SavedState 对象,将父类的状态传递给它。
  • 保存 ImageView 的 URI 到 SavedState 对象中。
  • 返回 SavedState 对象。

9.3 onRestoreInstanceState 方法

java 复制代码
// 恢复 ImageView 的状态
@Override
public void onRestoreInstanceState(Parcelable state) {
    // 如果状态不是 SavedState 类型,调用父类的 onRestoreInstanceState 方法
    if (!(state instanceof SavedState)) {
        super.onRestoreInstanceState(state); 
        return; 
    }
    // 转换为 SavedState 对象
    SavedState ss = (SavedState) state; 
    // 调用父类的 onRestoreInstanceState 方法恢复父类的状态
    super.onRestoreInstanceState(ss.getSuperState()); 
    // 恢复 ImageView 的 URI
    mUri = ss.uri; 
    if (mUri != null) {
        // 如果 URI 不为空,设置 ImageView 显示的图片资源为 URI 对应的图片
        setImageURI(mUri); 
    }
}

9.4 详细解释

  • 首先检查状态是否为 SavedState 类型,如果不是,调用父类的 onRestoreInstanceState 方法。
  • 将状态转换为 SavedState 对象。
  • 调用父类的 onRestoreInstanceState 方法恢复父类的状态。
  • 恢复 ImageView 的 URI,如果 URI 不为空,设置 ImageView 显示的图片资源为 URI 对应的图片。

十、总结与展望

10.1 总结

通过对 Android ImageView 的源码分析,我们深入了解了它的使用原理。ImageView 作为 Android 开发中常用的组件,具有强大的图片显示和处理能力。它的构造函数能够根据不同的场景初始化 ImageView 的属性;图片加载方法可以从不同的来源加载图片资源;缩放类型提供了多种图片显示方式;布局测量和绘制方法确保了图片能够正确地显示在界面上;事件处理机制使得 ImageView 可以响应用户的触摸操作;状态保存与恢复方法保证了 ImageView 在配置变化时能够正确地恢复状态。

10.2 展望

随着 Android 技术的不断发展,ImageView 也有很大的改进和扩展空间。未来,ImageView 可能会支持更多的图片格式和编码方式,提高图片加载的效率和质量。同时,可能会引入更强大的图片处理功能,如图片滤镜、图像识别等,为开发者提供更多的创意和可能性。此外,ImageView 在性能优化方面也有很大的提升空间,例如减少内存占用、提高绘制速度等,以满足用户对流畅体验的需求。总之,ImageView 作为 Android 开发中的重要组件,将在未来的发展中不断完善和创新。

以上是一篇关于 Android ImageView 使用原理的技术博客,由于篇幅限制,部分内容可能无法详细展开,但已经涵盖了 ImageView 的主要原理和关键源码分析。在实际编写时,可以根据需要进一步丰富和完善内容,确保字数达到 30000 字以上。同时,要注意代码的注释和逻辑的清晰性,让读者能够更好地理解和学习。

相关推荐
南客先生25 分钟前
马架构的Netty、MQTT、CoAP面试之旅
java·mqtt·面试·netty·coap
百锦再28 分钟前
Java与Kotlin在Android开发中的全面对比分析
android·java·google·kotlin·app·效率·趋势
Ya-Jun4 小时前
常用第三方库:flutter_boost混合开发
android·flutter·ios
_一条咸鱼_6 小时前
深度剖析:Android NestedScrollView 惯性滑动原理大揭秘
android·面试·android jetpack
_一条咸鱼_6 小时前
深度揭秘!Android NestedScrollView 绘制原理全解析
android·面试·android jetpack
_一条咸鱼_6 小时前
揭秘 Android CoordinatorLayout:从源码深度解析其协同工作原理
android·面试·android jetpack
_一条咸鱼_6 小时前
揭秘 Android View 的 TranslationY 位移原理:源码深度剖析
android·面试·android jetpack
_一条咸鱼_6 小时前
揭秘 Android NestedScrollView 滑动原理:源码深度剖析
android·面试·android jetpack
_一条咸鱼_6 小时前
深度揭秘:Android NestedScrollView 拖动原理全解析
android·面试·android jetpack
_小马快跑_6 小时前
重温基础:LayoutInflater.inflate(resource, root, attachToRoot)参数解析
android