Glide圆形图片实现:CircleCrop变换源码分析

Glide圆形图片实现:CircleCrop变换源码分析

【免费下载链接】glide An image loading and caching library for Android focused on smooth scrolling 【免费下载链接】glide 项目地址: https://gitcode.com/gh_mirrors/gl/glide

在Android应用开发中,圆形图片(CircleCrop)是一种常见的UI设计元素,广泛用于用户头像、图标等场景。Glide作为专注于平滑滚动的图片加载库,提供了便捷的CircleCrop变换功能。本文将深入分析Glide中CircleCrop变换的实现原理,从核心类结构到具体绘制流程,帮助开发者理解其工作机制并正确使用。

CircleCrop核心类结构

Glide的CircleCrop变换主要通过两个核心类实现:CircleCropTransformationUtils。其中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,提供了多种图片变换的静态方法,包括centerCropfitCentercircleCrop等。其中circleCrop方法是圆形裁剪的核心实现。

CircleCrop变换实现原理

调用流程

使用Glide加载圆形图片的典型代码如下:

Glide.with(context)
     .load(imageUrl)
     .circleCrop()
     .into(imageView);

这里的circleCrop()方法会为图片请求添加CircleCrop变换。在Glide的请求处理流程中,当图片加载完成后,会调用CircleCrop.transform()方法对图片进行处理。

核心裁剪逻辑

TransformationUtils.circleCrop()方法实现了具体的圆形裁剪逻辑,其核心步骤包括:

  1. 计算目标尺寸:取目标宽高中的较小值作为圆形直径
  2. 缩放原始图片:保持比例缩放,使图片能覆盖整个圆形区域
  3. 创建圆形遮罩:使用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混合模式来实现圆形裁剪。具体来说:

  1. 首先使用CIRCLE_CROP_SHAPE_PAINT绘制一个圆形作为遮罩
  2. 然后使用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加载圆形图片的基本方式有两种:

  1. 使用circleCrop()方法:
Glide.with(context)
     .load(imageUrl)
     .circleCrop()
     .into(imageView);
  1. 使用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);

性能优化建议

  1. 合理设置图片尺寸:CircleCrop会将图片缩放到目标尺寸,建议设置与ImageView匹配的尺寸,避免不必要的大图裁剪
  2. 使用硬件加速:确保ImageView启用硬件加速,提升绘制性能
  3. 避免过度绘制:如果父容器也是圆形,考虑设置android:clipToOutline="true"减少过度绘制
  4. 复用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

【免费下载链接】glide An image loading and caching library for Android focused on smooth scrolling 【免费下载链接】glide 项目地址: https://gitcode.com/gh_mirrors/gl/glide

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值