篇章目标要点
Glide是目前最为流行的图片加载框架,内部提供了完善的缓存机制和内存管理机制,此前已经通过3篇源码解析文章进行了分析。Glide的强大之处更在于其内部提供了丰富的图片转换接口,比如增加圆角,图片裁剪,高斯模糊等。但是Glide提供的图片处理的场景也是有限的,在开发过程中我们难免存在需要自行开发图片处理的场景。但是图片处理过程涉及Bitmap的创建和转换过程,如果处理不当则容易产生内存溢出,导致应用崩溃。那么此类需要进行自定义的场景我们则可以考虑如何充分复用Glide原来的功能和机制,降低自己的代码风险。本文计划结合如何实现带倒影的图片为例,阐述通过继承BitmapTransformation实现带倒影的图片。
适用场景
Glide原有的图片转换功能不能满足业务要求,需要自定义的场景。尽可能复用Glide原有框架,减少代码开发工作量和降低代码风险。
Glide原有转换工具的启示
以带圆角的图片实现过程为例,我们看下Glide原始代码是如何实现的。其基本思路是实现透明背景的画布,然后裁剪图片
@Override
protected Bitmap transform(
@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
//toTransform为原图
return TransformationUtils.roundedCorners(pool, toTransform, roundingRadius);
}
...
//生产圆角图片的核心代码
private static Bitmap roundedCorners(
@NonNull BitmapPool pool, @NonNull Bitmap inBitmap, DrawRoundedCornerFn drawRoundedCornerFn) {
// Alpha is required for this transformation.
Bitmap.Config safeConfig = getAlphaSafeConfig(inBitmap);
Bitmap toTransform = getAlphaSafeBitmap(pool, inBitmap);
//创建目标图片
Bitmap result = pool.get(toTransform.getWidth(), toTransform.getHeight(), safeConfig);
result.setHasAlpha(true);
//设置背景画布为透明,可以确保被裁剪的圆角位置无背景色
BitmapShader shader =
new BitmapShader(toTransform, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(shader);
RectF rect = new RectF(0, 0, result.getWidth(), result.getHeight());
BITMAP_DRAWABLE_LOCK.lock();
try {
Canvas canvas = new Canvas(result);
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
//将图片裁剪为带圆角的矩形
drawRoundedCornerFn.drawRoundedCorners(canvas, paint, rect);
//转换完毕清空画布
clear(canvas);
} finally {
BITMAP_DRAWABLE_LOCK.unlock();
}
if (!toTransform.equals(inBitmap)) {
pool.put(toTransform);
}
return result;
}
自定义转换类实现图片倒影
完成的效果图如下
(1)参照Glide源码思路自定义BitmapTransformation
本处自定义内容较为简单,主要内容是为转换类型定义一个唯一ID和引用生成带倒影图片的逻辑。代码如下
public class ReflectTransformation extends BitmapTransformation {
/**
* 为了符合Glide缓存机制的需要,需要针对不同类型的转换设置一个唯一ID
*/
private static final String TRANSFORMATIONS_ID = "com.bumptech.glide.transformations.ReflectTransformation";
private static final byte[] TRANSFORMATIONS_ID_BYTES = TRANSFORMATIONS_ID.getBytes(Charset.forName("UTF-8"));
private static final String TAG = "ReflectTransformation";
/**
* 此处设置计划进行的Bitmap转换的逻辑
* @param pool
* @param toTransform 原始的Bitmap
* @param outWidth ImageView的宽度
* @param outHeight ImageView的高度
* @return 返回转化后的Bitmap
*/
@Override
protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
Log.d(TAG,"prepare transform picture");
return ReflectionBitmapUtil.handleReflectBitmap(toTransform);
}
/**
* 更新硬盘缓存的索引key值
*/
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
messageDigest.update(TRANSFORMATIONS_ID_BYTES);
}
@Override
public int hashCode() {
return TRANSFORMATIONS_ID.hashCode();
}
@Override
public boolean equals(@Nullable Object obj) {
return obj instanceof ReflectTransformation;
}
}
(2)完成带倒影的Bitmap转换。
其基本动作是生产带倒影的图片,然后将倒影图片同渐变透明遮罩进行相交处理,将处理后的合成Bitmap与原图绘制在同一个Bitmap上即完成了任务
public class ReflectionBitmapUtil {
//本体图片与倒影之间的间隔
private static final int INTERVAL =2;
//倒影高度占据本体高度的比例
private static final float REFLECT_RATIO = 0.2f;
/**
* 生产带倒影的图片
* @param bitmap 待处理的原图
* @return 返回带倒影的Bitmap
*/
public static Bitmap handleReflectBitmap(Bitmap bitmap){
int width = bitmap.getWidth();
int height = bitmap.getHeight();
//创建带倒影图片的画布
Bitmap canvasBitmap = Bitmap.createBitmap(width , (int)(height*(1f+REFLECT_RATIO)+INTERVAL) , bitmap.getConfig());
Canvas canvas = new Canvas(canvasBitmap);
canvas.drawBitmap(bitmap , 0 ,0 , null);
//生成倒影Bimtap
Matrix matrix = new Matrix();
matrix.setScale(1 , -1);
Bitmap reflect = Bitmap.createBitmap(bitmap,0 , (int)(height*(1f-REFLECT_RATIO)) , width , (int)(height*REFLECT_RATIO) , matrix ,false);
canvas.drawBitmap(reflect , 0 , height+INTERVAL , null);
//透明渐变色遮罩,与倒影进行相交
LinearGradient gradient = new LinearGradient(0 ,0 ,0,reflect.getHeight() , 0x00ffffff , 0x50ffffff , Shader.TileMode.MIRROR);
Paint paint = new Paint();
paint.setShader(gradient);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
canvas.drawRect(0,height+INTERVAL , width , canvasBitmap.getHeight() , paint);
//回收倒影Bitmap和canvas画布
if(!reflect.isRecycled()){
reflect.recycle();
}
canvas.setBitmap(null);
return canvasBitmap;
}
}
(3)复用Glide方法调用
参照以下代码完全实现了沿用Glide方法和框架
public class MainActivity extends AppCompatActivity {
//计划加载的图片
private final static String PICTURE = "https://gss0.baidu.com/70cFfyinKgQFm2e88IuM_a/baike/pic/item/7ac880515fcd8e6c43a75b63.jpg";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView imageViewOrigin = findViewById(R.id.image_view_origin);
ImageView imageViewNew = findViewById(R.id.image_view_new);
//加载原图
Glide.with(imageViewOrigin).load(PICTURE).into(imageViewOrigin);
//加载转换后的图片(实现圆角+倒影)
MultiTransformation multiTransformation = new MultiTransformation(new RoundedCorners(10),new ReflectTransformation());
Glide.with(this).load(PICTURE).transform(multiTransformation).into(imageViewNew);
}
}
学习心得
通过参照学习Glide内部图片转换工具的代码原理,能够掌握复用Glide框架的基本上开发我们自己需要的图片转换组件,这种方式相对于自行进行Bitmap转换,在代码效率和内存消耗方面会更具优势