原文链接:http://www.fresco-cn.org/
Fresco 是一个强大的图片加载组件。
Fresco 中设计有一个叫做 image pipeline 的模块。它负责从网络,从本地文件系统,本地资源加载图片。为了最大限度节省空间和CPU时间,它含有3级缓存设计(2级内存,1级文件)。
Fresco 中设计有一个叫做 Drawees 模块,方便地显示loading图,当图片不再显示在屏幕上时,及时地释放内存和空间占用。
Fresco 支持 Android2.3(API level 9) 及其以上系统。
解压后的图片,即Android中的Bitmap
,占用大量的内存。大的内存占用势必引发更加频繁的GC。在5.0以下,GC将会显著地引发界面卡顿。
在5.0以下系统,Fresco将图片放到一个特别的内存区域。当然,在图片不显示的时候,占用的内存会自动被释放。这会使得APP更加流畅,减少因图片内存占用而引发的OOM。
Fresco 在低端机器上表现一样出色,你再也不用因图片内存占用而思前想后。
渐进式的JPEG图片格式已经流行数年了,渐进式图片格式先呈现大致的图片轮廓,然后随着图片下载的继续,呈现逐渐清晰的图片,这对于移动设备,尤其是慢网络有极大的利好,可带来更好的用户体验。
Android 本身的图片库不支持此格式,但是Fresco支持。使用时,和往常一样,仅仅需要提供一个图片的URI即可,剩下的事情,Fresco会处理。
是的,支持加载Gif图,支持WebP格式。
Fresco 的 Drawees 设计,带来一些有用的特性:
Fresco 的 image pipeline 设计,允许用户在多方面控制图片的加载:
如果你仅仅是想简单下载一张网络图片,在下载完成之前,显示一张占位图,那么简单使用SimpleDraweeView 即可。
为了下载网络图片,请确保在 AndroidManifest.xml
中有以下权限:
<uses-permission android:name="android.permission.INTERNET"/>
在 Application 初始化时,在应用调用 setContentView()
之前,进行初始化:
Fresco.initialize(context);
在xml布局文件中, 加入命名空间:
<!-- 其他元素 -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto">
加入SimpleDraweeView
:
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="20dp"
android:layout_height="20dp"
fresco:placeholderImage="@drawable/my_drawable"
/>
开始加载图片
Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/fresco-logo.png");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);
剩下的,Fresco会替你完成:
Fresco 支持许多URI格式。
特别注意:Fresco 不支持 相对路径的URI. 所有的URI都必须是绝对路径,并且带上该URI的scheme。
如下:
类型 | Scheme | 示例 |
---|---|---|
远程图片 | http://, https:// |
HttpURLConnection 或者参考 使用其他网络加载方案 |
本地文件 | file:// |
FileInputStream |
Content provider | content:// |
ContentResolver |
asset目录下的资源 | asset:// |
AssetManager |
res目录下的资源 | res:// |
Resources.openRawResource |
res 示例:
Uri uri = Uri.parse("res://包名(实际可以是任何字符串甚至留空)/" + R.drawable.ic_launcher);
注意,只有图片资源才能使用在Image pipeline中,比如(PNG)。其他资源类型,比如字符串,或者XML Drawable在Image pipeline中没有意义。所以加载的资源不支持这些类型。
像ShapeDrawable这样声明在XML中的drawable可能引起困惑。注意到这毕竟不是图片,如果想把这样的drawable作为图像显示。
那么把这个drawable设置为占位图,然后把URI设置为null。
Drawee 轻松支持圆角显示,并且显示圆角时,并不复制和修改Bitmap对象,那样太耗费内存。
圆角实际有2种呈现方式:
roundAsCircle
为trueroundedCornerRadius
设置圆角时,支持4个角不同的半径。XML中无法配置,但可在Java代码中配置。
可使用以下两种方式:
solid color
来绘制圆角。但是背景需要固定成指定的颜色。 在XML中指定roundWithOverlayColor
, 或者通过调用setOverlayColor
来完成此设定。 SimpleDraweeView
支持如下几种圆角配置:
<com.facebook.drawee.view.SimpleDraweeView
...
fresco:roundedCornerRadius="5dp"
fresco:roundBottomLeft="false"
fresco:roundBottomRight="false"
fresco:roundWithOverlayColor="@color/blue"
fresco:roundingBorderWidth="1dp"
fresco:roundingBorderColor="@color/red"
在创建 DraweeHierarchy 时,可以给 GenericDraweeHierarchyBuilder
指定一个RoundingParams 用来绘制圆角效果。
RoundingParams roundingParams = RoundingParams.fromCornersRadius(7f);
roundingParams.setOverlayColor(R.color.green);
// 或用 fromCornersRadii 以及 asCircle 方法
genericDraweeHierarchyBuilder
.setRoundingParams(roundingParams);
你也可以在运行时,改变圆角效果
RoundingParams roundingParams =
mSimpleDraweeView.getHierarchy().getRoundingParams();
roundingParams.setBorder(R.color.red, 1.0);
roundingParams.setRoundAsCircle(true);
mSimpleDraweeView.getHierarchy().setRoundingParams(roundingParams);
在运行时,不能改变呈现方式: 原本是圆角,不能改为圆圈。
当使用BITMAP_ONLY
(默认)模式时的限制:
BitmapDrawable
和 ColorDrawable
类的图片可以实现圆角。我们目前不支持包括NinePatchDrawable
和 ShapeDrawable
在内的其他类型图片。(无论他们是在XML或是程序中声明的)BitmapShader
的限制,当一个图片不能覆盖全部的View的时候,边缘部分会被重复显示,而非留白。对这种情况可以使用不同的缩放类型(比如centerCrop)来保证图片覆盖了全部的View。 OVERLAY_COLOR
模式没有上述限制,但由于这个模式使用在图片上覆盖一个纯色图层的方式来模拟圆角效果,因此只有在图标背景是静止的并且与图层同色的情况下才能获得较好的效果。
Drawee 内部实现了一个CLIPPING
模式。但由于有些Canvas
的实现并不支持路径剪裁(Path Clipping),这个模式被禁用了且不对外开放。并且由于路径剪裁不支持反锯齿,会导致圆角的边缘呈现像素化的效果。
总之,如果生成临时bitmap的方法,所有的上述问题都可以避免。但是这个方法并不被支持因为这会导致很严重的内存问题。
综上所述,在 Android 中实现圆角效果,没有一个绝对好的方案,你必须在上述的方案中进行选择。
注意: 本页提及的API仅是初步设计,后续可能变动
Fresco 支持渐进式的网络JPEG图。在开始加载之后,图会从模糊到清晰渐渐呈现。
你可以设置一个清晰度标准,在未达到这个清晰度之前,会一直显示占位图。
渐进式JPEG图仅仅支持网络图。
配置Image pipeline时 需要传递一个 ProgressiveJpegConfig. 的实例。
这个实例需要完成两个事情: 1. 返回下一个需要解码的扫描次数 2. 确定多少个扫描次数之后的图片才能开始显示。
下面的实例中,为了实现节省CPU,并不是每个扫描都进行解码。
注意:
getNextScanNumberToDecode
, 等待扫描值大于返回值,才有可能进行解码。 假设,随着下载的进行,下载完的扫描序列如下: 1, 4, 5, 10
。那么:
getNextScanNumberToDecode
返回为2, 因为初始时,解码的扫描数为0。ProgressiveJpegConfig pjpegConfig = new ProgressiveJpegConfig() {
@Override
public int getNextScanNumberToDecode(int scanNumber) {
return scanNumber + 2;
}
public QualityInfo getQualityInfo(int scanNumber) {
boolean isGoodEnough = (scanNumber >= 5);
return ImmutableQualityInfo.of(scanNumber, isGoodEnough, false);
}
}
ImagePipelineConfig config = ImagePipelineConfig.newBuilder()
.setProgressiveJpegConfig(pjpeg)
.build();
除了自己实现ProgressiveJpegConfig, 也可以直接使用SimpleProgressiveJpegConfig.
目前,我们必须显式地在加载时,允许渐进式JPEG图片加载。
Uri uri;
ImageRequest request = ImageRequestBuilder
.newBuilderWithSource(uri)
.setProgressiveRenderingEnabled(true)
.build();
PipelineDraweeController controller = Fresco.newControllerBuilder()
.setImageRequest(requests)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
我们希望在后续的版本中,在setImageURI
方法中可以直接支持渐进式图片加载。
Fresco 支持 GIF 和 WebP 格式的动画图片。对于 WebP 格式的动画图的支持包括扩展的 WebP 格式,即使 Android 2.3及其以后那些没有原生 WebP 支持的系统。
如果你希望图片下载完之后自动播放,同时,当View从屏幕移除时,停止播放,只需要在image request 中简单设置,如下:
Uri uri;
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setAutoPlayAnimations(true)
. // other setters
.build();
mSimpleDraweeView.setController(controller);
也许你希望在代码中直接控制动画的播放。这种情况下,你需要监听图片是否加载完毕,然后才能控制动画的播放:
ControllerListener controllerListener = new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(
String id,
@Nullable ImageInfo imageInfo,
@Nullable Animatable anim) {
if (anim != null) {
// app-specific logic to enable animation starting
anim.start();
}
};
Uri uri;
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setControllerListener(controllerListener)
// other setters
.build();
mSimpleDraweeView.setController(controller);
另外,controller提供对Animatable 的访问。
如果有可用动画的话,可对动画进行灵活的控制:
Animatable animation = mSimpleDraweeView.getController().getAnimatable();
if (animation != null) {
// 开始播放
animation.start();
// 一段时间之后,根据业务逻辑,停止播放
animation.stop();
}
动画现在还不支持 postprocessors 。
多图请求需 自定义ImageRequest.
假设你要显示一张高分辨率的图,但是这张图下载比较耗时。与其一直显示占位图,你可能想要先下载一个较小的缩略图。
这时,你可以设置两个图片的URI,一个是低分辨率的缩略图,一个是高分辨率的图。
Uri lowResUri, highResUri;
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setLowResImageRequest(ImageRequest.fromUri(lowResUri))
.setImageRequest(ImageRequest.fromUri(highResUri))
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
本功能仅支持本地URI,并且是JPEG图片格式
如果本地JPEG图,有EXIF的缩略图,image pipeline 可以立刻返回它作为一个缩略图。Drawee
会先显示缩略图,完整的清晰大图在 decode 完之后再显示。
Uri uri;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setLocalThumbnailPreviewsEnabled(true)
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
大部分时候,一张图片只有一个 URI。加载它,然后工作完成~
但是假设同一张图片有多个 URI 的情况。比如,你可能上传过一张拍摄的照片。原始图片太大而不能上传,所以图片首先经过了压缩。在这种情况下,首先尝试获取本地压缩后的图片 URI,如果失败的话,尝试获取本地原始图片 URI,如果还是失败的话,尝试获取上传到网络的图片 URI。直接下载我们本地可能已经有了的图片不是一件光彩的事。
Image pipeline 会首先从内存中搜寻图片,然后是磁盘缓存,再然后是网络或其他来源。对于多张图片,不是一张一张按上面的过程去做,而是 pipeline 先检查所有图片是否在内存。只有没在内存被搜寻到的才会寻找磁盘缓存。还没有被搜寻到的,才会进行一个外部请求。
使用时,创建一个image request 数组,然后传给 ControllerBuilder
:
Uri uri1, uri2;
ImageRequest request = ImageRequest.fromUri(uri1);
ImageRequest request2 = ImageRequest.fromUri(uri2);
ImageRequest[] requests = { request1, request2 };
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setFirstAvailableImageRequests(requests)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
这些请求中只有一个会被展示。第一个被发现的,无论是在内存,磁盘或者网络,都会是被返回的那个。pipeline 认为数组中请求的顺序即为优先顺序。
DataSource Supplier
为了更好的灵活性,你可以在创建 Drawee controller
时自定义 DataSource Supplier
。你可以以 FirstAvailiableDataSourceSupplier
,IncreasingQualityDataSourceSupplier
为例自己实现DataSource Supplier
,或者以AbstractDraweeControllerBuilder
为例将多个 DataSource Supplier
根据需求组合在一起。
你也许想在图片下载完成后执行一些动作,比如使某个别的 View
可见,或者显示一些文字。你也许还想在下载失败后做一些事,比如向用户显示一条失败信息。
图片是后台线程异步加载的,所以你需要某一方式来监听 DraweeController
传递的事件。我们可以使用一个 ControllerListener
实现事件的监听。
在监听事件回调时,无法修改图片,如果需要修改图片,可使用后处理器(Postprocessor)
简单定义一个ControllerListener
即可,推荐继承BaseControllerListener
:
ControllerListener controllerListener = new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(
String id,
@Nullable ImageInfo imageInfo,
@Nullable Animatable anim) {
if (imageInfo == null) {
return;
}
QualityInfo qualityInfo = imageInfo.getQualityInfo();
FLog.d("Final image received! " +
"Size %d x %d",
"Quality level %d, good enough: %s, full quality: %s",
imageInfo.getWidth(),
imageInfo.getHeight(),
qualityInfo.getQuality(),
qualityInfo.isOfGoodEnoughQuality(),
qualityInfo.isOfFullQuality());
}
@Override
public void onIntermediateImageSet(String id, @Nullable ImageInfo imageInfo) {
FLog.d("Intermediate image received");
}
@Override
public void onFailure(String id, Throwable throwable) {
FLog.e(getClass(), throwable, "Error loading %s", id)
}
};
Uri uri;
DraweeController controller = Fresco.newControllerBuilder()
.setControllerListener(controllerListener)
.setUri(uri);
// other setters
.build();
mSimpleDraweeView.setController(controller);
对所有的图片加载,onFinalImageSet
或者 onFailure
都会被触发。前者在成功时,后者在失败时。
如果允许呈现渐进式JPEG,同时图片也是渐进式图片,onIntermediateImageSet
会在每个扫描被解码后回调。具体图片的那个扫描会被解码,参见渐进式JPEG图
使用这个功能需要直接创建 image request。
Scaling 是一种画布操作,通常是由硬件加速的。图片实际大小保持不变,它只不过在绘制时被放大或缩小。
Resizing 是一种软件执行的管道操作。它返回一张新的,尺寸不同的图片。
Downsampling 同样是软件实现的管道操作。它不是创建一张新的图片,而是在解码时改变图片的大小。
Resize 很少是必要的。Scale 是大部分情况下的优先选择,即使在用 resize 时。
Resize 有以下几个限制:
Scale 并没有以上的限制,它使用 Android 内置的功能使图片和显示边界相符。在 Android 4.0 及之后,它可以通过 GPU 进行加速。这在大部分情况下是最快,同时也是最高效地将图片显示为你想要的尺寸的方式。唯一的缺点是当图片远大于显示大小时,会浪费内存。
那么什么时候该使用 resize 呢?你应该只在需要展示一张远大于显示大小的图片时使用 resize 以节省内存。一个例子是当你想要在 1280*720(大约 1MP) 的 view 中显示一张 8MP 的照片时。一张 8MP 的图片当解码为 4字节/像素的 ARGB 图片时大约占 32MB 的内存。如果 resize 为显示大小,它只占用少于 4MB 内存。
对于网络图片,在考虑 resize 之前,先尝试请求大小合适的图片。如果服务器能返回一张较小的图,就不要请求一张 8MP 的高解析度图片。你应该考虑用户的流量。同时,获取较小的图片可以减少你的 APP 的存储空间和 CPU 占用。
只有当服务器不提供可选的较小图片,或者你在使用本地图片时,你才应该采取 resize。在任何其他情况下,包括放大图片,都该使用 scale。对于 scale,只需要指定 SimpleDraweeView
中 layout_width
和 layout_height
的大小,就像在其他 Android View 中做的那样。然后指定缩放类型。
Resize 并不改变原始图片,它只在解码前修改内存中的图片大小。
相比 Android 内置的功能,这个方法可以进行更大范围的调整。尤其是通过相机拍摄的照片,对于 scale 来说通常太大,需要在显示前进行 resize。
目前仅支持 JPEG 格式图片的 resize 操作,但它是最常用的图片格式,且大多数安卓设备的相机照片存储为该格式。
如果要 resize,创建ImageRequest
时,提供一个 ResizeOptions :
Uri uri = "file:///mnt/sdcard/MyApp/myfile.jpg";
int width = 50, height = 50;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setResizeOptions(new ResizeOptions(width, height))
.build();
PipelineDraweeController controller = Fresco.newDraweeControllerBuilder()
.setOldController(mDraweeView.getController())
.setImageRequest(request)
.build();
mSimpleDraweeView.setController(controller);
向下采样是一个最近添加到 Fresco 的特性。使用的话需要在设置 image pipeline 时进行设置:
.setDownsampleEnabled(true)
如果开启该选项,pipeline 会向下采样你的图片,代替 resize 操作。你仍然需要像上面那样在每个图片请求中调用 setResizeOptions
。
向下采样在大部分情况下比 resize 更快。除了支持 JPEG 图片,它还支持 PNG 和 WebP(除动画外) 图片。
我们希望在将来的版本中默认开启此选项。
如果看到的图片是侧着的,用户会非常难受。许多设备会在 JPEG 文件的 metadata 中记录下照片的方向。如果你想图片呈现的方向和设备屏幕的方向一致,你可以简单地这样做到:
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setAutoRotateEnabled(true)
.build();
// as above
有时,我们想对从服务器下载,或者本地获取的图片做些修改,比如在某个坐标统一加个网格什么的。你可以使用 Postprocessor
,最好的方式是继承 BasePostprocessor。
下面的例子给图片加了红色网格:
Uri uri;
Postprocessor redMeshPostprocessor = new BasePostprocessor() {
@Override
public String getName() {
return "redMeshPostprocessor";
}
@Override
public void process(Bitmap bitmap) {
for (int x = 0; x < bitmap.getWidth(); x+=2) {
for (int y = 0; y < bitmap.getHeight(); y+=2) {
bitmap.setPixel(x, y, Color.RED);
}
}
}
}
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setPostprocessor(redMeshPostprocessor)
.build();
PipelineDraweeController controller = (PipelineDraweeController)
Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getController())
// other setters as you need
.build();
mSimpleDraweeView.setController(controller);
图片在进入后处理器(postprocessor)的图片是原图的一个完整拷贝,原来的图片不受修改的影响。在5.0以前的机器上,拷贝后的图片也在native内存中。
在开始一个图片显示时,即使是反复显示同一个图片,在每次进行显示时,都需要指定后处理器。对于同一个图片,每次显示可以使用不同的后处理器。
后处理器现在不支持动画图片。
可能会出现即时的后处理无法实现的情况。如果出现该情况,BasePostprocessor
还有一个接收两个参数的 process 方法。下面的例子实现了水平翻转图片:
@Override
public void process(Bitmap destBitmap, Bitmap sourceBitmap) {
for (int x = 0; x < destBitmap.getWidth(); x++) {
for (int y = 0; y < destBitmap.getHeight(); y++) {
destBitmap.setPixel(destBitmap.getWidth() - x, y, sourceBitmap.getPixel(x, y));
}
}
}
源图片和目标图片具有相同的大小。
如果处理后的图片大小需要和原图片不同,我们有第三个 process 方法。你可以使用PlatformBitmapFactory
类以指定的大小安全地创建一张图片,在 Java Heap 之外。
下面的例子将源图片复制为 1 / 4 大小。
@Override
public CloseableReference<Bitmap> process(
Bitmap sourceBitmap,
PlatformBitmapFactory bitmapFactory) {
CloseableReference<Bitmap> bitmapRef = bitmapFactory.createBitmap(
sourceBitmap.getWidth() / 2,
sourceBitmap.getHeight() / 2);
try {
Bitmap destBitmap = bitmapRef.get();
for (int x = 0; x < destBitmap.getWidth(); x+=2) {
for (int y = 0; y < destBitmap.getHeight(); y+=2) {
destBitmap.setPixel(x, y, sourceBitmap.getPixel(x, y));
}
}
return CloseableReference.cloneOrNull(bitmapRef);
} finally {
CloseableReference.closeSafely(bitmapRef);
}
}
你必须遵循 closeable references 的规则。
不要使用 Android 中 Bitmap.createBitmap() 方法,它会在 Java 堆内存中产生一个 bitmap 对象。
不要重写多于 1 个的 process 方法。这么做可能造成无法预测的结果。
你可以选择性地缓存后处理器的输出结果。它会和原始图片一起放在缓存里。
如果要这样做,你的后处理器必须实现 getPostprocessorCacheKey
方法,并返回一个非空的结果。
为实现缓存命中,随后的请求中使用的后处理器必须是同一个类并返回同样的键。否则,它的返回结果将会覆盖之前缓存的条目。
例子:
public class OperationPostprocessor extends BasePostprocessor {
private int myParameter;
public OperationPostprocessor(int param) {
myParameter = param;
}
public void process(Bitmap bitmap) {
doSomething(myParameter);
}
public CacheKey getPostprocessorCacheKey() {
return new MyCacheKey(myParameter);
}
}
如果你想要缓存总是命中,只需在 getPostprocessorCacheKey
中返回一个常量值。如果你的postprocessor
总是返回不同的结果,而你也不想要缓存命中,返回 null。
如果想对同一个图片进行多次后处理,那么继承BaseRepeatedPostprocessor即可。该类有一个update
方法,需要执行后处理时,调用该方法即可。
下面的例子展示了在运行时,后处理改变图片网格的颜色:
public class MeshPostprocessor extends BaseRepeatedPostprocessor {
private int mColor = Color.TRANSPARENT;
public void setColor(int color) {
mColor = color;
update();
}
@Override
public String getName() {
return "meshPostprocessor";
}
@Override
public void process(Bitmap bitmap) {
for (int x = 0; x < bitmap.getWidth(); x+=2) {
for (int y = 0; y < bitmap.getHeight(); y+=2) {
bitmap.setPixel(x, y, mColor);
}
}
}
}
MeshPostprocessor meshPostprocessor = new MeshPostprocessor();
// setPostprocessor as in above example
// 改变颜色
meshPostprocessor.setColor(Color.RED);
meshPostprocessor.setColor(Color.BLUE);
每个 image request, 应该只有一个Postprocessor
,但是这个后处理器是状态相关了。
根据 postprocessor 的性质,目标图片不会永远是完全不透明的。由于 Bitmap.hasAlpha
方法的返回值,这有时会导致问题。也就是说如果该方法返回 false(默认值),Android 会选择不进行混合地快速绘制。这会导致出现一张用黑色代替透明像素的半透明图片。为了解决这一问题,将目标图片中该值设为 true。
destinationBitmap.setHasAlpha(true);
如果你需要的ImageRequest
仅仅是一个URI,那么ImageRequest.fromURI
就足够了,在多图请求及图片复用中,有这样的用法。
否则,你需要ImageRequestBuilder
来做更多的事情。
Uri uri;
ImageDecodeOptions decodeOptions = ImageDecodeOptions.newBuilder()
.setBackgroundColor(Color.GREEN)
.build();
ImageRequest request = ImageRequestBuilder
.newBuilderWithSource(uri)
.setImageDecodeOptions(decodeOptions)
.setAutoRotateEnabled(true)
.setLocalThumbnailPreviewsEnabled(true)
.setLowestPermittedRequestLevel(RequestLevel.FULL_FETCH)
.setProgressiveRenderingEnabled(false)
.setResizeOptions(new ResizeOptions(width, height))
.build();
uri
- 唯一的必选的成员. 参考 支持的URIsautoRotateEnabled
- 是否支持自动旋转.progressiveEnabled
- 是否支持渐进式加载.postprocessor
- 后处理器(postprocess).resizeOptions
- 图片缩放选项,用前请先阅读缩放和旋转.Image pipeline 加载图片时有一套明确的请求流程
setLowestPermittedRequestLevel
允许设置一个最低请求级别,请求级别和上面对应地有以下几个取值:
BITMAP_MEMORY_CACHE
ENCODED_MEMORY_CACHE
DISK_CACHE
FULL_FETCH
如果你需要立即取到一个图片,或者在相对比较短时间内取到图片,否则就不显示的情况下,这非常有用。
总有一些时候,DraweeViews
是满足不了需求的,在展示图片的时候,我们还需要展示一些其他的内容,或者支持一些其他的操作。在同一个View里,我们可能会想显示一张或者多张图。
在自定义View中,Fresco 提供了两个类来负责图片的展现:
DraweeHolder
单图情况下用。MultiDraweeHolder
多图情况下用。 Android 呈现View对象,只有View对象才能得到一些系统事件的通知。DraweeViews
处理这些事件通知,高效地管理内存。使用DraweeHolder
时,你需要自己实现这几个方法。
如果没按照以下步骤实现的话,很可能会引起内存泄露
当图片不再在View上显示时,比如滑动时View滑动到屏幕外,或者不再绘制,图片就不应该再存在在内存中。Drawees 监听这些事情,并负责释放内存。当图片又需要显示时,重新加载。
这些在DraweeView
中是自动的,但是在自定义View中,需要我们自己去操作,如下:
DraweeHolder mDraweeHolder;
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
mDraweeHolder.onDetach();
}
@Override
public void onStartTemporaryDetach() {
super.onStartTemporaryDetach();
mDraweeHolder.onDetach();
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
mDraweeHolder.onAttach();
}
@Override
public void onFinishTemporaryDetach() {
super.onFinishTemporaryDetach();
mDraweeHolder.onAttach();
}
如果你启用了点击重新加载,在自定义View中,需要这样:
@Override
public boolean onTouchEvent(MotionEvent event) {
return mDraweeHolder.onTouchEvent(event) || super.onTouchEvent(event);
}
你必须调用
Drawable drawable = mDraweeHolder.getHierarchy().getTopLevelDrawable();
drawable.setBounds(...);
否则图片将不会出现。
Drawable.Callback
// When a holder is set to the view for the first time,
// don't forget to set the callback to its top-level drawable:
mDraweeHolder = ...
mDraweeHolder.getTopLevelDrawable().setCallback(this);
// In case the old holder is no longer needed,
// don't forget to clear the callback from its top-level drawable:
mDraweeHolder.getTopLevelDrawable().setCallback(null);
mDraweeHolder = ...
verifyDrawable:
@Override
protected boolean verifyDrawable(Drawable who) {
if (who == mDraweeHolder.getHierarchy().getTopLevelDrawable()) {
return true;
}
// 对其他Drawable的验证逻辑
}
invalidateDrawable
处理了图片占用的那块区域。这同样需要非常小心和细致
我们推荐如下实现构造函数:
init
方法。init
方法中执行初始化操作。 即,不要在构造函数中用 this
来调用另外一个构造。
这样可以保证,不管调用哪个构造,都可以正确地执行初始化流程。holder 在 init
方法中被创建。
如果有可能,只在 View 创建时,创建 Drawees。创建 DraweeHierarchy 开销较大,最好只做一次。
class CustomView extends View {
DraweeHolder<GenericDraweeHierarchy> mDraweeHolder;
// constructors following above pattern
private void init() {
GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources());
.set...
.set...
.build();
mDraweeHolder = DraweeHolder.create(hierarchy, context);
}
}
使用 controller builder 创建DraweeController,然后调用holder的 setController
方法,而不是设置给自定义 View。
DraweeController controller = Fresco.newControllerBuilder()
.setUri(uri)
.setOldController(mDraweeHolder.getController())
.build();
mDraweeHolder.setController(controller);
和 DraweeHolder
相比,MultiDraweeHolder
有 add
, remove
, clear
等方法可以操作 Drawees。如下:
MultiDraweeHolder<GenericDraweeHierarchy> mMultiDraweeHolder;
private void init() {
GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources());
.set...
.build();
mMultiDraweeHolder = new MultiDraweeHolder<GenericDraweeHierarchy>();
mMultiDraweeHolder.add(new DraweeHolder<GenericDraweeHierarchy>(hierarchy, context));
// repeat for more hierarchies
}
同样,也需要处理系统事件,设置声音等等,就像处理单个DraweeHolder
那样。