前不久做一个水平图片滑动列表选择,效果类似于QQ发送图片。因为图片的长宽比不一致,而高度是固定的,所以就要对图片进行特殊拉伸。所以就看了下Glide拉伸这一块的源码。废话不多说了,下面开始总结。
平时我们用BitmapFactory进行加载较大图片时候,往往先通过inJustDecodeBounds方式解析出图片宽高,再结合ImageView宽高计算inSampleSize(即缩放比例),然后再“按需”加载bitmap,并显示出来。
那在Glide中是如何实现拉伸的呢?
其实Glide中实现也是上述思路,只是封装起来了。Glide负责计算缩放比例并decode的类是Downsampler,其中的getRoundedSampleSize 就负责计算缩放比例的。
public abstract class Downsampler implements BitmapDecoder<InputStream>{
//这个方法decode图片得到bitmap,其实最终也会调用BitmapFactory.decode()方法
@Override
public Bitmap decode(InputStream is, BitmapPool pool, int outWidth, int outHeight, DecodeFormat decodeFormat) {
...省略...
//这里计算缩放比例
final int sampleSize = getRoundedSampleSize(degreesToRotate, inWidth, inHeight, outWidth, outHeight);
//正式解析
final Bitmap downsampled = downsampleWithSize(invalidatingStream, bufferedStream, options, pool, inWidth, inHeight, sampleSize,decodeFormat);
...省略...
}
...
//计算缩放比例
private int getRoundedSampleSize(int degreesToRotate, int inWidth, int inHeight, int outWidth, int outHeight) {
int targetHeight = outHeight == Target.SIZE_ORIGINAL ? inHeight : outHeight;
int targetWidth = outWidth == Target.SIZE_ORIGINAL ? inWidth : outWidth;
final int exactSampleSize;
//注意下面的getSampleSize(),这个才是根据图片和Taget宽高计算缩放比例的。这个方法在下面有
if (degreesToRotate == 90 || degreesToRotate == 270) {
exactSampleSize = getSampleSize(inHeight, inWidth, targetWidth, targetHeight);
} else {
exactSampleSize = getSampleSize(inWidth, inHeight, targetWidth, targetHeight);
}
final int powerOfTwoSampleSize = exactSampleSize == 0 ? 0 : Integer.highestOneBit(exactSampleSize);
// Although functionally equivalent to 0 for BitmapFactory, 1 is a safer default for our code than 0.
return Math.max(1, powerOfTwoSampleSize);
}
//getSampleSize,这个核心方法是抽象方法,根据图片宽高(inWith和inHeight)和
// Target宽高(outWidth和outHeight)来计算缩放比例
protected abstract int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight);
}
如上代码展示,最核心的抽象方法getSampleSize才是决定如何缩放的,而Downsampler目前仅提供3种实现,分别是:
public static final Downsampler AT_LEAST = new Downsampler() {
@Override
protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) {
return Math.min(inHeight / outHeight, inWidth / outWidth);
}
...
};
public static final Downsampler AT_MOST = new Downsampler() {
@Override
protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) {
int maxIntegerFactor = (int) Math.ceil(Math.max(inHeight / (float) outHeight,
inWidth / (float) outWidth));
int lesserOrEqualSampleSize = Math.max(1, Integer.highestOneBit(maxIntegerFactor));
return lesserOrEqualSampleSize << (lesserOrEqualSampleSize < maxIntegerFactor ? 1 : 0);
}
...
};
public static final Downsampler NONE = new Downsampler() {
@Override
protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) {
return 0;
}
...
};
这三种方式,在Glide加载图片时如何应用的呢? 我们在Glide.with(context).load(path).asBitmap() 时会得到BitmapRequestBuilder,下面是它的部分代码。可以得知调用它的approximate() 、atMost() 、asIs()分别对应上面的AT_LEAST、ATMOST、以及None
public class BitmapRequestBuilder<ModelType, TranscodeType>
extends GenericRequestBuilder<ModelType, ImageVideoWrapper, Bitmap, TranscodeType> implements BitmapOptions {
public BitmapRequestBuilder approximate() {
return downsample(Downsampler.AT_LEAST);
}
//指定不缩放
public BitmapRequestBuilder asIs() {
return downsample(Downsampler.NONE);
}
public BitmapRequestBuilder atMost() {
return downsample(Downsampler.AT_MOST);
}
//遗憾这个是私有方法,不能自定义
private BitmapRequestBuilder downsample(Downsampler downsampler) {
this.downsampler = downsampler;
imageDecoder = new StreamBitmapDecoder(downsampler, bitmapPool, decodeFormat);
super.decoder(new ImageVideoBitmapDecoder(imageDecoder, videoDecoder));
return this;
}
}
到此, Glide的缩放原理就比较清楚了。遗憾的是,downsample是私有方法,这就意味着我们不能自定义一个Downsampler。不知是否还有其他地方可以设置