Glide圆形图片实现:CircleCrop变换源码分析
在Android应用开发中,圆形图片(CircleCrop)是一种常见的UI设计元素,广泛用于用户头像、图标等场景。Glide作为专注于平滑滚动的图片加载库,提供了便捷的CircleCrop变换功能。本文将深入分析Glide中CircleCrop变换的实现原理,从核心类结构到具体绘制流程,帮助开发者理解其工作机制并正确使用。
CircleCrop核心类结构
Glide的CircleCrop变换主要通过两个核心类实现:CircleCrop和TransformationUtils。其中CircleCrop是变换接口的具体实现,而TransformationUtils则提供了实际的图片裁剪逻辑。
CircleCrop类定义
CircleCrop类位于library/src/main/java/com/bumptech/glide/load/resource/bitmap/CircleCrop.java,继承自BitmapTransformation,是Glide图片变换体系的一部分。其核心代码如下:
public class CircleCrop extends BitmapTransformation {
private static final int VERSION = 1;
private static final String ID = "com.bumptech.glide.load.resource.bitmap.CircleCrop." + VERSION;
private static final byte[] ID_BYTES = ID.getBytes(CHARSET);
@Override
protected Bitmap transform(
@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
return TransformationUtils.circleCrop(pool, toTransform, outWidth, outHeight);
}
@Override
public boolean equals(Object o) {
return o instanceof CircleCrop;
}
@Override
public int hashCode() {
return ID.hashCode();
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
messageDigest.update(ID_BYTES);
}
}
可以看到,CircleCrop类非常简洁,主要实现了三个方法:
transform():调用TransformationUtils.circleCrop()完成实际的图片裁剪equals()和hashCode():用于缓存键的生成updateDiskCacheKey():更新磁盘缓存键
TransformationUtils类
TransformationUtils是Glide的图片变换工具类,位于library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java,提供了多种图片变换的静态方法,包括centerCrop、fitCenter、circleCrop等。其中circleCrop方法是圆形裁剪的核心实现。
CircleCrop变换实现原理
调用流程
使用Glide加载圆形图片的典型代码如下:
Glide.with(context)
.load(imageUrl)
.circleCrop()
.into(imageView);
这里的circleCrop()方法会为图片请求添加CircleCrop变换。在Glide的请求处理流程中,当图片加载完成后,会调用CircleCrop.transform()方法对图片进行处理。
核心裁剪逻辑
TransformationUtils.circleCrop()方法实现了具体的圆形裁剪逻辑,其核心步骤包括:
- 计算目标尺寸:取目标宽高中的较小值作为圆形直径
- 缩放原始图片:保持比例缩放,使图片能覆盖整个圆形区域
- 创建圆形遮罩:使用Canvas绘制圆形,并通过PorterDuff混合模式实现裁剪
关键代码如下:
public static Bitmap circleCrop(
@NonNull BitmapPool pool, @NonNull Bitmap inBitmap, int destWidth, int destHeight) {
int destMinEdge = Math.min(destWidth, destHeight);
float radius = destMinEdge / 2f;
// 计算缩放比例
float scaleX = destMinEdge / (float) inBitmap.getWidth();
float scaleY = destMinEdge / (float) inBitmap.getHeight();
float maxScale = Math.max(scaleX, scaleY);
// 计算缩放后的图片尺寸和位置
float scaledWidth = maxScale * inBitmap.getWidth();
float scaledHeight = maxScale * inBitmap.getHeight();
float left = (destMinEdge - scaledWidth) / 2f;
float top = (destMinEdge - scaledHeight) / 2f;
RectF destRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
// 获取安全的Bitmap(确保包含alpha通道)
Bitmap toTransform = getAlphaSafeBitmap(pool, inBitmap);
Bitmap.Config outConfig = getAlphaSafeConfig(inBitmap);
Bitmap result = pool.get(destMinEdge, destMinEdge, outConfig);
result.setHasAlpha(true);
// 加锁绘制(处理部分设备的线程安全问题)
BITMAP_DRAWABLE_LOCK.lock();
try {
Canvas canvas = new Canvas(result);
// 绘制圆形遮罩
canvas.drawCircle(radius, radius, radius, CIRCLE_CROP_SHAPE_PAINT);
// 绘制图片内容(通过SRC_IN模式实现裁剪)
canvas.drawBitmap(toTransform, null, destRect, CIRCLE_CROP_BITMAP_PAINT);
clear(canvas);
} finally {
BITMAP_DRAWABLE_LOCK.unlock();
}
// 回收临时Bitmap
if (!toTransform.equals(inBitmap)) {
pool.put(toTransform);
}
return result;
}
绘制原理
CircleCrop使用了Android图形系统的PorterDuff混合模式来实现圆形裁剪。具体来说:
- 首先使用
CIRCLE_CROP_SHAPE_PAINT绘制一个圆形作为遮罩 - 然后使用
CIRCLE_CROP_BITMAP_PAINT绘制原始图片,该画笔设置了PorterDuff.Mode.SRC_IN混合模式
PorterDuff.Mode.SRC_IN模式表示:只显示源图像(原始图片)与目标图像(圆形遮罩)相交的部分,且采用源图像的像素。这样就实现了将矩形图片裁剪为圆形的效果。
相关画笔定义在TransformationUtils中:
private static final Paint CIRCLE_CROP_SHAPE_PAINT = new Paint(CIRCLE_CROP_PAINT_FLAGS);
private static final Paint CIRCLE_CROP_BITMAP_PAINT;
static {
CIRCLE_CROP_BITMAP_PAINT = new Paint(CIRCLE_CROP_PAINT_FLAGS);
CIRCLE_CROP_BITMAP_PAINT.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
}
线程安全处理
在TransformationUtils中,我们注意到有一个BITMAP_DRAWABLE_LOCK锁对象:
private static final Set<String> MODELS_REQUIRING_BITMAP_LOCK =
new HashSet<>(Arrays.asList(
// Moto X gen 2
"XT1085", "XT1092", ...,
// Moto G gen 1
"XT1031", "XT1028", ...));
private static final Lock BITMAP_DRAWABLE_LOCK =
MODELS_REQUIRING_BITMAP_LOCK.contains(Build.MODEL) ? new ReentrantLock() : new NoLock();
这是因为在部分旧设备(如特定型号的Moto手机)上,Bitmap绘制操作不是线程安全的。Glide通过设备型号检测,为需要的设备添加同步锁,确保绘制操作的线程安全。
测试用例分析
Glide为CircleCrop变换编写了完善的单元测试,位于library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/CircleCropTest.java。测试用例覆盖了多种场景:
方形图片裁剪测试
@Test
public void testTransform_withSquare() {
Bitmap redSquare = createSolidRedBitmap(50, 50);
Bitmap result = circleCrop.transform(bitmapPool, redSquare, 50, 50);
Bitmap expected = createBitmapWithRedCircle(50, 50);
assertSamePixels(expected, result);
}
该测试验证了对正方形图片进行圆形裁剪的正确性,通过比较实际结果与预期圆形图片的每个像素来确保裁剪效果。
矩形图片裁剪测试
@Test
public void testTransform_withWideRectangle() {
Bitmap redWideRectangle = createSolidRedBitmap(100, 50);
Bitmap result = circleCrop.transform(bitmapPool, redWideRectangle, 80, 50);
Bitmap expected = createBitmapWithRedCircle(80, 50);
assertSamePixels(expected, result);
}
该测试验证了对宽矩形图片进行圆形裁剪的效果,确保图片会先按比例缩放再裁剪为圆形。
Bitmap复用测试
@Test
public void testTransform_reusesBitmap() {
Bitmap toReuse = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
when(bitmapPool.get(50, 50, Bitmap.Config.ARGB_8888)).thenReturn(toReuse);
Bitmap redSquare = createSolidRedBitmap(50, 50);
Bitmap result = circleCrop.transform(bitmapPool, redSquare, 50, 50);
assertEquals(toReuse, result);
}
该测试验证了Glide的BitmapPool复用机制,确保裁剪操作会优先复用池中已有的Bitmap对象,减少内存分配和垃圾回收。
实际应用场景
基本用法
使用Glide加载圆形图片的基本方式有两种:
- 使用
circleCrop()方法:
Glide.with(context)
.load(imageUrl)
.circleCrop()
.into(imageView);
- 使用
apply()方法结合RequestOptions:
Glide.with(context)
.load(imageUrl)
.apply(RequestOptions.circleCropTransform())
.into(imageView);
圆形图片效果展示
圆形裁剪效果可参考Glide的instrumentation测试资源中的示例图片,如instrumentation/src/main/res/drawable/dog.jpg经过CircleCrop变换后,会从矩形图片变为圆形图片。
与其他变换组合使用
CircleCrop可以与其他变换组合使用,例如先缩放后裁剪:
Glide.with(context)
.load(imageUrl)
.transform(new MultiTransformation<>(
new ScaleTransformation(2.0f),
new CircleCrop()
))
.into(imageView);
性能优化建议
- 合理设置图片尺寸:CircleCrop会将图片缩放到目标尺寸,建议设置与ImageView匹配的尺寸,避免不必要的大图裁剪
- 使用硬件加速:确保ImageView启用硬件加速,提升绘制性能
- 避免过度绘制:如果父容器也是圆形,考虑设置
android:clipToOutline="true"减少过度绘制 - 复用Bitmap:Glide的BitmapPool已内置复用机制,无需额外处理
总结
Glide的CircleCrop变换通过BitmapTransformation接口实现,核心逻辑在TransformationUtils.circleCrop()方法中。它使用PorterDuff混合模式实现圆形裁剪,并通过设备型号检测和同步锁确保线程安全。开发者可以通过简单的API调用实现圆形图片加载,同时Glide内部处理了图片缩放、内存复用等性能优化。
通过本文的分析,我们了解了CircleCrop变换的实现细节,包括:
- 核心类结构与调用流程
- 圆形裁剪的绘制原理
- 线程安全处理机制
- 测试用例设计
- 实际应用与性能优化
掌握这些知识,有助于开发者更好地使用Glide的图片变换功能,解决实际开发中遇到的问题。
官方源码:library/src/main/java/com/bumptech/glide/load/resource/bitmap/CircleCrop.java
工具类实现:library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java
测试用例:library/test/src/test/java/com/bumptech/glide/load/resource/bitmap/CircleCropTest.java
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



