在Android开发中,使用Glide加载网络图片时,经常需要同时处理占位图和圆角效果。一些开发者会在图片加载完成后通过自定义View或者Canvas裁剪来实现圆角,但这种做法容易导致内存占用上升,并且无法对占位图生效。实际上Glide的变换机制可以统一处理原始图片的圆角,但占位图的圆角需要结合资源本身或控件配置来达成。理解这一差异是正确实现圆角占位图的关键。

一、从RequestOptions看占位图的基础配置
Glide 4.x之后的API将加载配置统一封装在RequestOptions中,通过placeholder、error和fallback三个方法可以分别设置加载中占位图、加载失败占位图和空数据占位图。这些占位图会在不同加载状态下展示给用户,避免图片加载期间出现空白区域。基础配置代码非常简洁:
// Glide 4.x 基础占位图配置
RequestOptions options = new RequestOptions()
.placeholder(R.drawable.ic_placeholder)
.error(R.drawable.ic_error)
.fallback(R.drawable.ic_fallback)
.centerCrop();
Glide.with(context)
.load(imageUrl)
.apply(options)
.into(imageView);
需要注意的是,这里配置的占位图是以Drawable形式直接设置到目标控件上的,它并不会经过Glide的变换管线。也就是说,即便你在RequestOptions中增加了圆角Transform,占位图依然会以原始Drawable的形状显示。如果占位图是一个矩形图片,那么它在加载过程中就是直角效果,只有在真正图片加载完成后才会出现圆角。这一点常常被忽略,导致界面在加载过程中出现直角与圆角混用的不一致体验。
要解决占位图圆角问题,最直接的方式是将占位图资源本身就制作成圆角Drawable。Android的shape drawable支持定义圆角矩形,可以作为占位图使用。例如在res/drawable目录下创建rounded_placeholder.xml,内容如下:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#EEEEEE" />
<corners android:radius="24dp" />
</shape>
将这个shape作为placeholder即可保证占位图本身就是圆角矩形。如果需要显示网络占位图,则可以先用Glide将占位图下载并裁剪成圆角位图,再通过Drawable形式设置,但这种方式成本较高,一般只在特定场景使用。
二、使用Glide自带的RoundedCorners处理加载图圆角
Glide从4.x版本开始内置了RoundedCorners转换器,它继承自BitmapTransformation,可以对加载成功的图片进行圆角裁剪。RoundedCorners构造参数接收圆角半径,单位是像素,而不是dp。使用时需要配合centerCrop或fitCenter等缩放方式,否则圆角效果可能不理想。示例代码如下:
RequestOptions options = new RequestOptions()
.placeholder(R.drawable.rounded_placeholder)
.error(R.drawable.rounded_error)
.transform(new RoundedCorners(24)) // 24px圆角
Glide.with(context)
.load(imageUrl)
.apply(options)
.into(imageView);
RoundedCorners的工作原理是在BitmapShader基础上创建一个带圆角的Paint,然后将原始Bitmap绘制到目标Bitmap上。它会保留图片的宽高比例,但不会改变图片的实际尺寸。如果原图尺寸远大于控件尺寸,建议先使用centerCrop或override指定宽高,否则裁剪后的圆角图片仍然很大,容易造成不必要的内存浪费。
很多开发者会误以为RoundedCorners也能对占位图生效,实际上它的Transform只在图片资源加载完成后执行。占位图仍然是未经过变换的Drawable。因此,如果需要占位图也显示圆角,必须像前面介绍的那样使用圆角Drawable资源。如果占位图是普通的位图资源,也可以通过先对位图做圆角处理再设置,但实现较繁琐。更简单的方案是使用Material Components库中的ShapeableImageView来替代普通ImageView,它支持对包括占位图在内的所有绘制内容进行统一的圆角裁剪。
ShapeableImageView的使用方式如下:
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image"
android:layout_width="80dp"
android:layout_height="80dp"
app:shapeAppearanceOverlay="@style/RoundedImageStyle"
android:scaleType="centerCrop" />
通过定义圆角样式,这个控件可以对内部任何Drawable或Bitmap进行圆角显示,包括Glide设置的占位图和加载后的图片。这种方式不需要依赖Glide的Transform,能够保持占位图与加载图在视觉上的一致性。
三、自定义BitmapTransformation实现更灵活的圆角效果
当项目需要定制圆角策略时,可以继承BitmapTransformation自定义变换器。例如需要只对图片的顶部两个角进行圆角,或者需要为图片添加圆角描边,这些需求Glide自带的RoundedCorners无法直接满足。自定义Transformation可以完全控制Canvas绘制过程,实现任意复杂的形状。
下面是一个专用于实现四角圆角的CustomRoundedCorners类,它使用BitmapShader绘制圆角矩形,并通过BitmapPool复用Bitmap以降低内存抖动:
public class CustomRoundedCorners extends BitmapTransformation {
private final float radius;
public CustomRoundedCorners(float radius) {
this.radius = radius;
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
if (toTransform == null) return null;
Bitmap result = pool.get(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
if (result == null) {
result = Bitmap.createBitmap(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
}
result.setHasAlpha(true);
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(new BitmapShader(toTransform, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
RectF rect = new RectF(0f, 0f, toTransform.getWidth(), toTransform.getHeight());
canvas.drawRoundRect(rect, radius, radius, paint);
return result;
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
messageDigest.update(("CustomRoundedCorners" + radius).getBytes(StandardCharsets.UTF_8));
}
}
其中updateDiskCacheKey方法必须正确实现,它会将自定义参数写入Glide的缓存键中。如果忘记重写该方法,使用不同圆角半径加载同一张图片时可能会因缓存键相同而错误复用缓存,导致圆角半径不一致。因此自定义Transformation一定要保证缓存键包含所有影响输出结果的参数。
使用自定义Transformation时,可以将其作为单例常量复用,例如:
public final class Transformations {
public static final CustomRoundedCorners ROUNDED_16 = new CustomRoundedCorners(16);
public static final CustomRoundedCorners ROUNDED_32 = new CustomRoundedCorners(32);
}
然后在RequestOptions中引用这些单例即可。避免在每次加载时创建新的Transformation实例,可以有效降低对象分配频率和GC压力。
四、圆角占位图在列表场景中的性能与缓存优化
在RecyclerView等列表场景中,图片加载密度高,如果每一项都在onBindViewHolder中创建新的RequestOptions或Transformation实例,会导致内存分配频繁,进而引发卡顿。最佳实践是将配置好的RequestOptions和Transformation作为静态常量或成员变量持有,并在初始化时完成构建。例如:
private final RequestOptions roundedOptions = new RequestOptions()
.placeholder(R.drawable.shape_rounded_placeholder)
.error(R.drawable.shape_rounded_error)
.transform(Transformations.ROUNDED_16);
由于RequestOptions内部包含多个字段,重复使用同一个实例可以显著减少额外开销。同时,Glide会将变换后的Bitmap存入磁盘缓存和内存缓存,自定义Transformation的缓存键设计直接影响缓存命中率。如果缓存键中没有包含圆角半径参数,不同圆角需求之间会互相污染;如果包含了所有参数,则不同圆角效果会生成不同的缓存副本,虽然占用更多存储空间但能保证显示正确。
另一个容易忽略的性能点是占位图资源的大小。如果占位图是一张很大的位图,Glide在显示占位图时也会消耗一定内存。建议使用shape drawable或经过压缩的小尺寸位图作为占位图,避免在快速滚动列表中重复解码大图。对于占位图的圆角处理,优先使用shape drawable或ShapeableImageView,不要依赖Glide的Transform,因为占位图不经过变换管线,强行处理只会增加复杂度。
最后,当图片加载完成后,Glide会替换ImageView中的Drawable。如果使用ShapeableImageView,圆角裁剪由控件本身完成,不受Drawable替换影响,界面显示更加稳定。如果使用普通ImageView配合圆角Drawable占位图,加载完成后图片会变成直角或由RoundedCorners裁剪后的圆角,需要确保两者圆角半径视觉一致,否则会出现切换瞬间的跳动感。
综合来看,推荐的做法是:占位图和加载图都采用一致的圆角视觉,占位图使用圆角shape drawable,加载图使用Glide的RoundedCorners或自定义Transformation。若追求更统一的显示效果,可以引入ShapeableImageView作为承载控件,配合Glide的基础加载配置,这样能最大程度减少占位图与加载图之间的视觉差异,同时保持代码清晰和良好的性能表现。
Glide圆角占位图RequestOptions修改时间:2026-08-20 14:27:37