ImageView是Android开发中最基础的图片显示控件,核心用法就是通过setImageResource()或setImageBitmap()把图片资源塞进去,配合ScaleType控制缩放模式,但真正写好它需要处理内存复用、加载策略和裁剪细节。
很多初级开发者在ListView或RecyclerView里直接加载本地图片,结果滑动卡顿甚至OOM崩溃,问题不在ImageView本身,而是没有理解它的工作边界,下面直接拆解日常开发里最常用的几个代码场景,从基础属性到进阶封装,每一步都给出可复制的写法。
ImageView基础用法与src和background的区别
ImageView在XML布局里最常用的属性是src和background,新手经常混用,但两者的行为差异很大。
src设置的是图片内容,会按照scaleType属性缩放;background设置的是背景,默认拉伸填充整个View区域,如果同时设置两者,前景图会盖在背景上面,实际开发中,背景通常放纯色或渐变Drawable,内容图放src,这样逻辑清晰,也方便做占位图。
<ImageView
android:id="@+id/iv_avatar"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/ic_default_avatar"
android:background="@drawable/bg_circle_white"
android:scaleType="centerCrop" />
scaleType是最容易忽略的属性,它决定了图片在View里怎么缩放,常用的几个值:
centerCrop:按比例放大或缩小使得图片填满View,超出部分裁剪,适合头像、封面图。fitCenter:按比例缩放使得整张图完整显示在View内,可能留白,适合展示完整图片。fitXY:不保持比例,拉伸填满View,容易变形,一般不用。center:不做缩放,居中显示原图,大图会溢出不显示。
Java/Kotlin代码里动态设置也一样:
imageView.setImageResource(R.drawable.ic_place_holder) imageView.scaleType = ImageView.ScaleType.CENTER_CROP
ImageView加载大图OOM怎么解决
这是百度搜索里出现频率很高的长尾词,“imageview加载大图oom怎么解决”,一张几MB的图片直接setImageResource(),在内存里解码后可能膨胀到几十MB,低端机上必崩。
行业共识是:不要在UI线程直接解码大图,正确做法是先读取图片的尺寸信息,再根据ImageView的实际大小进行采样压缩。
fun loadSampledBitmap(resId: Int, reqWidth: Int, reqHeight: Int): Bitmap {
val boundsOptions = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeResource(resources, resId, boundsOptions)
val inSampleSize = calculateInSampleSize(boundsOptions, reqWidth, reqHeight)
val decodeOptions = BitmapFactory.Options().apply {
this.inSampleSize = inSampleSize
inPreferredConfig = Bitmap.Config.RGB_565
}
return BitmapFactory.decodeResource(resources, resId, decodeOptions)
}
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
val (height: Int, width: Int) = options.outHeight to options.outWidth
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight = height / 2
val halfWidth = width / 2
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize = 2
}
}
return inSampleSize
}
这个calculateInSampleSize方法就是业内常用的采样率计算逻辑。inJustDecodeBounds为true时只读尺寸不解码像素,开销极小,拿到采样率后再正式解码,内存占用可以降到原来的1/4甚至1/8。
inPreferredConfig设为RGB_565,每个像素只占2字节,相比默认的ARGB_8888少一半内存,但注意,RGB_565不支持透明通道,带透明度的PNG图片会显示黑色背景,需要保留透明度的场景不要用这个配置。
ImageView和Glide怎么选:对比与配合
很多开发者纠结“imageview和glide区别”,其实两者不是替代关系,Glide本身就是基于ImageView工作的,Glide负责加载、缓存、解码,最后把Bitmap或者Drawable设置到ImageView上。
选型建议:
- 简单场景、少量本地图片:直接用ImageView配合BitmapFactory,不引入额外依赖。
- 网络图片、列表加载、需要缓存:用Glide,它能解决大部分性能问题。
Glide.with(this)
.load("https://example.com/image.jpg")
.placeholder(R.drawable.ic_default)
.error(R.drawable.ic_error)
.override(300, 300)
.centerCrop()
.into(imageView)
override()指定加载尺寸,源码内部会先查缓存,再决定是否压缩,列表滑动时Glide自动复用ImageView的bitmap,避免GC抖动。实际项目中,90%以上的图片加载任务交给Glide是稳妥的,自己写原生解码容易踩坑。
但Glide也不是万能,如果图片需要频繁修改像素(比如拍照后的水印合成),还是得用原生Bitmap操作,Glide适合展示,不适合加工。
ImageView实现圆角和圆形图片的代码示例
UI设计里圆角头像、圆形logo是标配,实现方式有两种:原生ShapeableImageView或者自定义BitmapShader。
ShapeableImageView是Material库提供的控件,代码简洁,适合圆角固定不变的场景。
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_round"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_avatar"
app:shapeAppearanceOverlay="@style/ShapeAppearance.App.CornerSize" />
对应样式:
<style name="ShapeAppearance.App.CornerSize">
<item name="cornerSize">16dp</item>
</style>
圆形只要把cornerSize设为50%即可,这种方式处理静态UI高效,但动态改圆角大小比较麻烦。
自定义BitmapShader适合需要动态控制圆角程度的场景,核心思路是,把Bitmap作为Shader绘制到圆角矩形路径里。
class RoundImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
private val path = Path()
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private var radius = 0f
override fun onDraw(canvas: Canvas) {
val bitmap = getBitmapFromDrawable() ?: return
val shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
paint.shader = shader
path.reset()
path.addRoundRect(RectF(0f, 0f, width.toFloat(), height.toFloat()), radius, radius, Path.Direction.CW)
canvas.drawPath(path, paint)
}
}
这段代码里的getBitmapFromDrawable()需要自己实现,从drawable里取出Bitmap,注意Glide加载的图片要等回调拿到Bitmap后再设置给自定义View。
ImageView在RecyclerView中的复用与性能优化
列表滑动卡顿的常见原因就是ImageView在复用过程中频繁触发解码。RecyclerView的ViewHolder复用机制下,ImageView被反复setImageResource,每次都是重新解码,这是性能瓶颈。
优化思路有三个层面:
- 使用Glide并开启缓存,
diskCacheStrategy设为ALL,避免重复网络请求。 - 在
onBindViewHolder里使用Glide.with(holder.itemView.context).load(...).into(holder.imageView),不要自己new Bitmap。
- 图片尺寸提前裁剪,列表缩略图用
override()固定到列表项大小,避免加载原图。
override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = dataList[position] Glide.with(holder.itemView.context) .load(item.thumbnailUrl) .override(200, 200) .centerCrop() .into(holder.imageView)}固定尺寸的override配合centerCrop,是列表图片性能优化的标准写Fa,Glide会先查内存缓存,再查磁盘缓存,最后才走网络,滑动时上一个Item的加载请求会被自动取消,不会出现图片错位。
图片错位问题排查
错位的根源是ViewHolder复用导致旧图片还没来得及显示,新请求就到了,Glide的into()方法内部会处理这种情况,但如果你用原生setImageBitmap,必须在绑定数据时先清空ImageView:
holder.imageView.setImageDrawable(null)
这一行放在加载新图之前,能避免大部分错位问题。
Q&A:ImageView高频疑问解答
ImageView的adjustViewBounds属性有什么用
当ImageView的宽高至少有一个是wrap_content时,adjustViewBounds设为true可以让ImageView的尺寸根据图片的宽高比自动调整,典型的场景是聊天界面里的图片消息,宽度固定,高度根据图片比例自适应,注意,这个属性只在src有效时起作用,对background无效。
ImageView设置图片后内存仍然偏高怎么办
先用BitmapFactory.Options的inJustDecodeBounds检查图片原始尺寸,如果原始尺寸是4000×3000,而显示区域只有200×200,采样率至少设置为8,另外检查是否引用了大图资源,Android drawable目录的密度限定也会影响解码后的像素尺寸,把图片放到drawable-nodpi可以避免自动缩放,如果用了Glide,确认override()设置了合理的尺寸,并且skipMemoryCache()没有被误调用。
Glide加载的图片怎么转成Bitmap操作
在into()之前用addListener或者在asBitmap()回调里获取,推荐用asBitmap()配合into(new SimpleTarget,但SimpleTarget已废弃,新版本用CustomTarget,拿到Bitmap后做水印、裁剪、滤镜处理,再setImageBitmap给ImageView,注意CustomTarget持有外部Context时需要处理生命周期,避免内存泄漏。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/556433.html




