当下最常用的图片加载框架是:Gilde,Fresco,Picasso。Fresco是Facebook提供的开源图片加载库,它能够从网络,本地存储和Android资源文件中加载图片,且具有三级缓存设计(2级内存,1级文件)。Fresco中实现了各种加载过程以及加载后的图片绘制,整体都很强大。所以准备来好好学学这个框架啦。
Frescp框架的设计主要采用的是MVC模式。DraweeView实现了View的功能,DraweeHierarchy实现了Model的功能,DraweeController实现Controller的功能。
基本用法
首先在build.gradle中添加依赖,目前已经更新到1.8.1版本了:
dependencies {
// 在 API < 14 上的机器支持 WebP 时,需要添加
//compile 'com.facebook.fresco:animated-base-support:1.8.1'
// 支持 GIF 动图,需要添加
//compile 'com.facebook.fresco:animated-gif:1.8.1'
// 支持 WebP (静态图+动图),需要添加
//compile 'com.facebook.fresco:animated-webp:1.8.1'
//compile 'com.facebook.fresco:webpsupport:1.8.1'
// 仅支持 WebP 静态图,需要添加
compile 'com.facebook.fresco:webpsupport:1.8.1'
}
在Android studio会自动下载相应的依赖包。
在进行图片加载之前,需要配置Fresco类,Fresco.initialize只需要调用一次,所以我们在Application中进行初始化:
public class MyApplication extends Application {
@Override
public void onCreate(){
super.onCreate();
Fresco.initialize(this);
}
}
在AndroidManifest.xml中配置MyApplication,如果要从网络下载图片,还需要添加网络访问权限:
在xml中配置SimpleDraweeView
在Activity中加载图片:
SimpleDraweeView sdv = (SimpleDraweeView) findViewById(R.id.id_main_sdv);
Uri uri = Uri.parse("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1523079111408&di=7783555b20885592a8034c6e729a6414&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01ea90595f5ca4a8012193a3d93648.jpeg");
sdv.setImageURI(uri);
接下来就是等待Fresco下载完图片并展示出来了。
XML中配置使用Drawees
一个大致的XML配置例示如下:
注意的是,在设置宽和高的时候,一定要设置成固定值,因为下载的图片,原始的占位图片,出错后的图片,这些的尺寸可能各不相同,如果用wrap_content,View将重新layout。
在一种情况下可以用wrap_content,就是我们设置了viewAspectRatio属性确定宽高比,这样我们将其中一个属性设为定值,另一个属性设为warp_content,可以通过计算宽高比得到目标值。
DraweeHierarchy和DraweeHierarchyBuilder
在Java中配置需要用到DraweeHierarchy,如前面所说,DraweeHierarchy类似于MVC中的Model,所以我们对最后显示的drawable的显示设置都在着里面进行。
下面的代码实现了跟上面XML基本一致的功能,至于dp转float我就没去弄了。
//新建一个DraweeHierarchyBuilder类
GenericDraweeHierarchyBuilder builder =new GenericDraweeHierarchyBuilder(getResources());
//设置的到图片的缩放类型
builder.setActualImageScaleType(ScalingUtils.ScaleType.FOCUS_CROP);
//缩放类型为focusCrop时,需要设定一个居中点
//(0f,0f)表示左上对齐显示,(1f,1f)表示右下对齐显示
PointF pf = new PointF(1f,1f);
builder.setActualImageFocusPoint(pf);
//进度条,占位图片消失,加载图片展现的时间间隔
builder.setFadeDuration(1000);
//加载失败之后显示的图片及图片缩放类型
builder.setFailureImage(R.drawable.imgbg, ScalingUtils.ScaleType.CENTER_INSIDE);
//设置占位图片及缩放类型
builder.setPlaceholderImage(R.drawable.imgbg, ScalingUtils.ScaleType.FIT_CENTER);
//加载进度条图片及缩放类型
builder.setProgressBarImage(R.drawable.progress_bar, ScalingUtils.ScaleType.CENTER_INSIDE);
//提示重新加载的图片及缩放类型
builder.setRetryImage(R.mipmap.ic_launcher, ScalingUtils.ScaleType.CENTER_CROP);
//设置背景图片
builder.setBackground(getResources().getDrawable(R.color.colorWhite));
//在图片上方覆盖一个图片资源
builder.setOverlay(getResources().getDrawable(R.drawable.overlay));
builder.setPressedStateOverlay(getResources().getDrawable(R.color.colorBlack));
RoundingParams rp = new RoundingParams();
//是否要将图片剪切成圆形
rp.setRoundAsCircle(false);
//设置哪个角需要变成圆角
rp.setCornersRadii(100f,0f,100f,0f);
//圆角部分填充色
rp.setOverlayColor(getResources().getColor(R.color.colorWhite));
//边框宽度
rp.setBorderWidth(20f);
//边框填充色
rp.setBorderColor(getResources().getColor(R.color.colorBlack));
builder.setRoundingParams(rp);
//得到DraweeHierarchy实例
GenericDraweeHierarchy hierachy = builder.build();
//在Drawee中设置DraweeHierarchy
sdv.setHierarchy(hierachy);
除了这些之外,在Java中还可以设置在图片上方覆盖多个图片资源,我们也可以在加载的时候使用Fresco自定义的加载条。
//多个图片资源
List overlaysList;
builder.setOverlays(overlaysList);
//进度条
builder.setProgressBarImage(new ProgressBarDrawable());
上面基本介绍了MVC中的Model和View,接下来就来介绍一下Controller了。
DraweeController和DraweeControllerBuilder
DraweeController主要是用于对图片进行更多的控制和定制,我们可以设置对加载事件进行监听,对加载之后的图片进行压缩或者修改,可以在加载失败之后重新加载图片,也可以实现多图请求。
在代码中,我们利用DraweeControllerBuilder生成一个DraweeController实例,再将其传给SimpleDraweeView就可以了。
PipelineDraweeControllerBuilder sdcb = Fresco.newDraweeControllerBuilder();
//设置uri
sdcb.setUri(uri);
//加载失败之后,点击提示重新加载的图片资源重新加载
sdcb.setTapToRetryEnabled(true);
//在指定一个新的controller的时候,使用setOldController,这可节省不必要的内存分配。
sdcb.setOldController(sdv.getController());
DraweeController controller = sdcb.build();
sdv.setController(controller);
在Controller中,如果我们想要对下载事件进行监听,需要为Controller设置一个自定义的ControllerListener。
ControllerListener
在ControllerListener中可以对我们的下载过程进行监听。代码如下
ControllerListener listener = new BaseControllerListener(){
@Override
public void onSubmit(String id, Object callerContext) {
//提交请求之前调用的方法
Log.d(TAG, "onSubmit: " + id);
}
@Override
public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
// 所有图片都加载成功时触发的方法
Log.d(TAG, "onFinalImageSet: " + id);
}
@Override
public void onIntermediateImageSet(String id, ImageInfo imageInfo) {
//当中间图片下载成功的时候触发,用于多图请求
}
@Override
public void onIntermediateImageFailed(String id, Throwable throwable) {
//当中间图片下载失败的时候触发,用于多图请求
}
@Override
public void onFailure(String id, Throwable throwable) {
// 加载图片失败时回调的方法
Log.d(TAG, "onFailure: " + id);
}
@Override
public void onRelease(String id) {
//释放图片资源时加载的方法
Log.d(TAG, "onRelease: " + id);
}
};
PipelineDraweeControllerBuilder sdcb = Fresco.newDraweeControllerBuilder();
sdcb.setControllerListener(listener);
如果我们在请求图片的过程中,需要进行更多的操作的话,我们需要用ImageRequest来进行实现。
ImageRequest
ImageRequest支持很多的功能,例如自动旋转图片,渐进式加载,图片缩放,后处理器,图片复用,最低请求级别等等。基本的实现源码如下:
ImageRequest request = ImageRequestBuilder
//设置URI
.newBuilderWithSource(uri)
//自动旋转
.setAutoRotateEnabled(true)
//最低级别请求
.setLowestPermittedRequestLevel(ImageRequest.RequestLevel.FULL_FETCH)
//图片缩放
.setResizeOptions(new ResizeOptions(width,height))
//渐进式加载
.setProgressiveRenderingEnabled(false)
.build();
PipelineDraweeControllerBuilder sdcb = Fresco.newDraweeControllerBuilder();
sdcb.setImageRequest(request);
DraweeController controller = sdcb.build();
sdv.setController(controller);
最低请求级别包含以下几种取值:
BITMAP_MEMORY_CACHE |
检查内存缓存,有如,立刻返回。这个操作是实时的。 |
ENCODED_MEMORY_CACHE |
检查未解码的图片缓存,如有,解码并返回。 |
DISK_CACHE |
检查磁盘缓存,如果有加载,解码,返回。 |
FULL_FETCH |
下载或者加载本地文件。调整大小和旋转(如有),解码并返回。 |
假设一张图片具有多个URI,例如某张图片有各种比例的压缩图。我们在加载的时候,按顺序查找这些URI对应的图片是否存在,一旦找到第一个存在的,就进行展示。这也能有效避免我们去加载过大的图片。实现代码如下:
ImageRequest request1 = ImageRequest.fromUri(uri1);
ImageRequest request2 = ImageRequest.fromUri(uri);
ImageRequest[] request = {request1,request2};
PipelineDraweeControllerBuilder sdcb = Fresco.newDraweeControllerBuilder();
sdcb.setFirstAvailableImageRequests(request);
DraweeController controller = sdcb.build();
sdv.setController(controller);
后处理器:PostProcessor
PostProcessor主要用于对加载完的图片进行处理,图片在进入后处理器(postprocessor)的图片是原图的一个完整拷贝,原来的图片不受修改的影响。在5.0以前的机器上,拷贝后的图片也在native内存中。
在开始一个图片显示时,即使是反复显示同一个图片,在每次进行显示时,都需要指定后处理器。对于同一个图片,每次显示可以使用不同的后处理器。
通常通过继承BaseProcessor来实现处理,通过重写Process方法来进行图片处理。BaseProcessor中提供了三个不同输入形参的process方法来给我们实现重写。
public class MyPostprocessor extends BasePostprocessor {
@Override
public String getName() {
return "redMeshPostprocessor";
}
//对图片进行即时后处理
//代码中在图片加上红点
@Override
public void process(Bitmap bitmap) {
for (int x = 0; x < bitmap.getWidth(); x+=4) {
for (int y = 0; y < bitmap.getHeight(); y+=4) {
bitmap.setPixel(x, y, Color.RED);
}
}
}
//对图片无法进行即时处理
//代码中对图片进行反转,目标图片的处理需要用到原始图片的信息
@Override
public void process(Bitmap destBitmap, Bitmap sourceBitmap) {
super.process(destBitmap, sourceBitmap);
for (int x = 0; x < destBitmap.getWidth(); x++) {
for (int y = 0; y < destBitmap.getHeight(); y++) {
destBitmap.setPixel(destBitmap.getWidth() - 1 - x, y, sourceBitmap.getPixel(x, y));
}
}
}
//只用于处理后图片和源图片大小不一样的情况
//代码中将图片长宽都压缩为原来的1/4
@Override
public CloseableReference process(
Bitmap sourceBitmap,
PlatformBitmapFactory bitmapFactory) {
int scale = 4;
CloseableReference bitmapRef = bitmapFactory.createBitmap(
sourceBitmap.getWidth() / scale,
sourceBitmap.getHeight() / scale);
try {
Bitmap destBitmap = bitmapRef.get();
for (int x = 0; x < destBitmap.getWidth(); x++) {
for (int y = 0; y < destBitmap.getHeight(); y++) {
destBitmap.setPixel(x, y, sourceBitmap.getPixel(scale*x, scale*y));
}
}
return CloseableReference.cloneOrNull(bitmapRef);
} finally {
CloseableReference.closeSafely(bitmapRef);
}
}
}
说到这里,对于process方法,看起来像是直接对图片进行处理,那是怎么保证对原图的拷贝呢。这里看一下BaseProcessor的代码。
public void process(Bitmap destBitmap, Bitmap sourceBitmap) {
internalCopyBitmap(destBitmap, sourceBitmap);
process(destBitmap);
}
private static void internalCopyBitmap(Bitmap destBitmap, Bitmap sourceBitmap) {
if (destBitmap.getConfig() == sourceBitmap.getConfig()) {
Bitmaps.copyBitmap(destBitmap, sourceBitmap);
} else {
// The bitmap configurations might be different when the source bitmap's configuration is
// null, because it uses an internal configuration and the destination bitmap's configuration
// is the FALLBACK_BITMAP_CONFIGURATION. This is the case for static images for animated GIFs.
Canvas canvas = new Canvas(destBitmap);
canvas.drawBitmap(sourceBitmap, 0, 0, null);
}
}
从上面可以看出,在调用process之前,会先对原图进行复制,之后再进行process(destBitmap)操作。同样,看一下BaseProcessor中process(sourceBitmap,bitmapFactory)的定义:
@Override
public CloseableReference process(
Bitmap sourceBitmap,
PlatformBitmapFactory bitmapFactory) {
final Bitmap.Config sourceBitmapConfig = sourceBitmap.getConfig();
CloseableReference destBitmapRef =
bitmapFactory.createBitmapInternal(
sourceBitmap.getWidth(),
sourceBitmap.getHeight(),
sourceBitmapConfig != null ? sourceBitmapConfig : FALLBACK_BITMAP_CONFIGURATION);
try {
process(destBitmapRef.get(), sourceBitmap);
return CloseableReference.cloneOrNull(destBitmapRef);
} finally {
CloseableReference.closeSafely(destBitmapRef);
}
}
可以看出,在BaseProcessor中几个process的调用顺序分别是:
BaseProcessor中process(sourceBitmap,bitmapFactory)->process(destBitmap,sourceBitmap)->process(destBitmap)
在教程上说不能重写多个process方法,其实实际上是可以的,而且利用这个还能叠加自定义操作,例如反转之后加红点什么的。
在DraweeController中添加PostProcessor的代码如下:
//后处理器
Postprocessor processor = new MyPostprocessor();
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri).setPostprocessor(processor).build();
PipelineDraweeControllerBuilder sdcb = Fresco.newDraweeControllerBuilder();
sdcb.setImageRequest(request);
DraweeController controller = sdcb.build();
sdv.setController(controller);
关于Fresco的基本使用大概就是这样了。渐进式JPEG图,动画支持,多图请求这些因为暂时没有用到(没有对应图片),就等到以后用到的时候再来补充了。