第 2 章 DRAWEE 指南
最后更新于:2022-04-01 02:24:06
[TOC]
### 在XML中使用Drawees
Drawees 具有极大的可定制性。
下面的例子给出了可以配置的各种选项:
~~~xml
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="20dp"
android:layout_height="20dp"
fresco:fadeDuration="300"
fresco:actualImageScaleType="focusCrop"
fresco:placeholderImage="@color/wait_color"
fresco:placeholderImageScaleType="fitCenter"
fresco:failureImage="@drawable/error"
fresco:failureImageScaleType="centerInside"
fresco:retryImage="@drawable/retrying"
fresco:retryImageScaleType="centerCrop"
fresco:progressBarImage="@drawable/progress_bar"
fresco:progressBarImageScaleType="centerInside"
fresco:progressBarAutoRotateInterval="1000"
fresco:backgroundImage="@color/blue"
fresco:overlayImage="@drawable/watermark"
fresco:pressedStateOverlayImage="@color/red"
fresco:roundAsCircle="false"
fresco:roundedCornerRadius="1dp"
fresco:roundTopLeft="true"
fresco:roundTopRight="false"
fresco:roundBottomLeft="false"
fresco:roundBottomRight="true"
fresco:roundWithOverlayColor="@color/corner_color"
fresco:roundingBorderWidth="2dp"
fresco:roundingBorderColor="@color/border_color"
/>
~~~
##### 必须设置layout_width和layout_height
如果没有在XML中声明这两个属性,将无法正确加载图像。
##### wrap_content
*Drawees 不支持 `wrap_content` 属性。*
所下载的图像可能和占位图尺寸不一致,如果设置出错图或者重试图的话,这些图的尺寸也可能和所下载的图尺寸不一致。
如果大小不一致,图像下载完之后,假设如果是`wrap_content`,View将会重新layout,改变大小和位置。这将会导致界面跳跃。
##### 固定宽高比
只有希望显示的固定宽高比时,可以使用`wrap_content`。
如果希望显示的图片保持一定宽高比例,如果 4:3,则在XML中:
~~~xml
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="20dp"
android:layout_height="wrap_content"
<!-- other attributes -->
~~~
然后在代码中指定显示比例:
~~~java
mSimpleDraweeView.setAspectRatio(1.33f);
~~~
### 在JAVA代码中使用Drawees
#### 设置或更改要显示的图片
~~~
mSimpleDraweeView.setImageURI(uri);
~~~
如果要更加复杂的配置,可使用[ControllerBuilder](#);
#### 自定义显示图
一般情况下,在[XML设置显示效果即可](#), 如果想更多定制化,可以这样:
创建一个 builder 然后设置给 DraweeView:
~~~java
List<Drawable> backgroundsList;
List<Drawable> overlaysList;
GenericDraweeHierarchyBuilder builder =
new GenericDraweeHierarchyBuilder(getResources());
GenericDraweeHierarchy hierarchy = builder
.setFadeDuration(300)
.setPlaceholderImage(new MyCustomDrawable())
.setBackgrounds(backgroundList)
.setOverlays(overlaysList)
.build();
mSimpleDraweeView.setHierarchy(hierarchy);
~~~
对于同一个View,请不要多次调用`setHierarchy`,即使这个View是可回收的。创建 DraweeHierarchy 的较为耗时的一个过程,应该多次利用。
如果要改变所要显示的图片可使用`setController` 或者 `setImageURI`。
#### 修改 DraweeHierarchy
DraweeHierarchy 的一些属性可以在运行时改变。
要改变这些属性,首先获取一个引用:
~~~java
GenericDraweeHierarchy hierarchy = mSimpleDraweeView.getHierarchy();
~~~
##### 修改占位图
修改占位图为资源id:
~~~java
hierarchy.setPlaceholderImage(R.drawable.placeholderId);
~~~
或者修改为一个 [Drawable](http://developer.android.com/reference/android/graphics/drawable/Drawable.html):
~~~java
Drawable drawable;
// 创建一个drawable
hierarchy.setPlaceholderImage(drawable);
~~~
##### 修改显示的图像
修改[缩放类型](#):
~~~java
hierarchy.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_INSIDE);
~~~
当然,如果修改为 `focusCrop,` 需要指定一个居中点:
~~~java
hierarchy.setActualImageFocusPoint(point);
~~~
或者设置一个color filter:
~~~java
ColorFilter filter;
// 创建filter
hierarchy.setActualImageColorFilter(filter);
~~~
##### 圆角
All of the [rounding related params](#), except the rounding method, can be modified. You get a `RoundingParams` object from the hierarchy, modify it, and set it back again:
除了圆角显示方式(原来为圆角的不能修改为圆圈,反之亦然),其他圆角相关的呈现参数, [具体参见这里](#) 是可以动态修改的。
如下: 获取DraweeHierarchy的圆角显示参数,修改圆角半径为10。
~~~java
RoundingParams roundingParams = hierarchy.getRoundingParams();
roundingParams.setCornersRadius(10);
hierarchy.setRoundingParams(roundingParams);
~~~
### Drawee的各种效果配置
### 内容导航
- [定义](#)
- [设置要加载的图片](#)
- [占位图](#)
- [加载失败时的占位图](#)
- [点击重新加载](#)
- [显示一个进度条](#)
- [Backgrounds](#)
- [Overlays](#)
- [Pressed State Overlay](#)
### 定义
本页说明如何设置实现不同的图片呈现效果。
除了要加载的图片,其他各个设置都可以在xml中指定。在xml中指定的时候,可以是 `drawable/`下的资源,也可以颜色。
在Java 代码中也可以指定。如果需要 [通过程序设定](#) 的话会接触到这个类: GenericDraweeHierarchyBuilder
通过代码设置是,设置的值可以是资源id,也可以是 Drawable 的子类。
创建完 GenericDraweeHierarchy 之后,也可以通过该类的相关方法,重新设置一些效果。
大多数的用户呈现不同效果的drawables都是可以[缩放的](#).
### 设置要加载的图
除了需要加载的图片是真正必须的,其他的都是可选的。如前所述,图片可以来自多个地方。
所需加载的图片实际是DraweeController的一个属性,而不是 DraweeHierarchy 的属性。
可使用`setImageURI`方法或者通过设置 DraweeController 来进行设置。
对于要加载的图片,除了可以设置缩放类型外,DraweeHierarchy 还公开出一些其他方法用来控制显示效果:
- focus point (居中焦点, 用于 focusCrop 缩放模式)
- color filter
默认的缩放类型是: `centerCrop`
### 占位图(Placeholder)
在调用`setController` 或者 `setImageURI` 之后,占位图开始显示,直到图片加载完成。
对于渐进式格式的JPEG图片,占位图会显示直到满足已加载的图片解析度到达设定值。
XML 中属性值: `placeholderImage`
Hierarchy builder中的方法: `setPlaceholderImage`
Hierarchy method: `setPlaceholderImage`
默认值: a transparent [ColorDrawable](http://developer.android.com/reference/android/graphics/drawable/ColorDrawable.html)
默认缩放类型: `centerInside`
### 设置加载失败占位图
如果URI是无效的,或者下载过程中网络不可用,将会导致加载失败。当加载图片出错时,你可以设置一个出错提示图片。
XML 中属性值: `failureImage`
Hierarchy builder中的方法: `setFailureImage`
默认值: The placeholder image
默认缩放类型: `centerInside`
### 点击重新加载图
在加载失败时,可以设置点击重新加载。这时提供一个图片,加载失败时,会显示这个图片(而不是失败提示图片),提示用户点击重试。
在[ControllerBuilder](#) 中如下设置:
~~~
.setTapToRetryEnabled(true)
~~~
加载失败时,image pipeline 会重试四次;如果还是加载失败,则显示加载失败提示图片。
XML 中属性值: `retryImage`
Hierarchy builder中的方法: `setRetryImage`
默认值: The placeholder image
默认缩放类型: `centerInside`
### 显示一个进度条
设置一个进度条图片,提示用户正在加载。目前,进度条仅仅是提示正在loading,和加载进度无关。
XML 中属性值: `progressBarImage`
Hierarchy builder中的方法: `setProgressBarImage`
默认值: None
默认缩放类型: `centerInside`
### 背景
背景图会最先绘制,在XML中只可以指定一个背景图,但是在JAVA代码中,可以指定多个背景图。
当指定一个背景图列表的时候,列表中的第一项会被首先绘制,绘制在最下层,然后依次往上绘制。
背景图片不支持缩放类型,会被强制到`Drawee`尺寸大小。
XML 中属性值: `backgroundImage`
Hierarchy builder中的方法: `setBackground,``setBackgrounds`
默认值: None
默认缩放类型: N/A
### 设置叠加图(Overlay)
叠加图会最后被绘制。
和背景图一样,XML中只可以指定一个,如果想指定多个,可以通过JAVA代码实现。
当指定的叠加图是一个列表的时候,列表第一个元素会被先绘制,最后一个元素最后被绘制到最上层。
同样的,不支持各种缩放类型。
XML 中属性值: `overlayImage`
Hierarchy builder中的方法: `setOverlay,``setOverlays`
默认值: None
默认缩放类型: N/A
### 设置按压状态下的叠加图
同样不支持缩放,用户按压DraweeView时呈现。
XML 中属性值: `pressedStateOverlayImage`
Hierarchy builder中的方法: `setPressedStateOverlay`
默认值: None
默认缩放类型: N/A
### 缩放
对于 Drawee 的[各种效果配置](#),其中一些是支持缩放类型的。
#### 可用的缩放类型
<table><thead><tr><th>类型</th> <th>描述</th></tr></thead><tbody><tr><td>center</td> <td>居中,无缩放</td></tr><tr><td>centerCrop</td> <td>保持宽高比缩小或放大,使得两边都大于或等于显示边界。居中显示。</td></tr><tr><td><a class="internal" href="#focusCrop">focusCrop</a></td> <td>同centerCrop, 但居中点不是中点,而是指定的某个点</td></tr><tr><td>centerInside</td> <td>使两边都在显示边界内,居中显示。<br/>如果图尺寸大于显示边界,则保持长宽比缩小图片。</td></tr><tr><td>fitCenter</td> <td>保持宽高比,缩小或者放大,使得图片完全显示在显示边界内。居中显示</td></tr><tr><td>fitStart</td> <td>同上。但不居中,和显示边界左上对齐</td></tr><tr><td>fitEnd</td> <td>同fitCenter, 但不居中,和显示边界右下对齐</td></tr><tr><td>fitXY</td> <td>不保存宽高比,填充满显示边界</td></tr><tr><td><a class="internal" href="#none">none</a></td> <td>如要使用tile mode显示, 需要设置为none</td></tr></tbody></table>
这些缩放类型和Android [ImageView](http://developer.android.com/reference/android/widget/ImageView.ScaleType.html) 支持的缩放类型几乎一样.
唯一不支持的缩放类型是`matrix.` Fresco 提供了`focusCrop` 作为补充。通常这个缩放效果更佳。
#### focusCrop
`centerCrop`缩放模式会保持长宽比,缩放图片,填充满显示边界,居中显示。这个缩放模式在通常情况下很有用。
但是对于人脸等图片时,一味地居中显示,这个模式可能会裁剪掉一些有用的信息。
以人脸图片为例,借助一些类库,我们可以识别出人脸所在位置。如果可以设置以人脸位置居中裁剪显示,那么效果会好很多。
Fresco的focusCrop缩放模式正是为此而设计。只要提供一个居中聚焦点,显示时就会**尽量**以此点为中心。
居中点是以相对方式给出的,比如(0.5f, 0.5f)就是居中显示,(0f, 0f)就是左上对齐显示。
如果要使用此缩放模式,首先指定缩放模式。在XML:
~~~xml
fresco:actualImageScaleType="focusCrop"
~~~
在Java代码中
~~~java
PointF focusPoint;
// your app populates the focus point
mSimpleDraweeView
.getHierarchy()
.setActualImageFocusPoint(focusPoint);
~~~
#### none
如果你要使用tile mode进行显示,那么需要将scale type 设置为none.
### 圆角和圆圈
Drawee 轻松支持圆角显示,并且显示圆角时,并不复制和修改Bitmap对象,那样太耗费内存。
#### 圆角
圆角实际有2中呈现方式:
1. 圆圈 - 设置`roundAsCircle`为true
1. 圆角 - 设置`roundedCornerRadius`
设置圆角时,支持4个角不同的半径。XML中无法配置,但可在Java代码中配置。
#### 设置圆角
可使用以下两种方式:
1. 默认使用一个shader绘制圆角,但是仅仅占位图所要显示的图有圆角效果。失败示意图和重下载示意图无圆角效果。
1. 叠加一个`solid color`来绘制圆角。但是背景需要固定成指定的颜色。在XML中指定 `roundWithOverlayColor`, 或者通过调用`setOverlayColor`来完成此设定。
#### XML中配置
`SimpleDraweeView` 支持如下几种圆角配置:
~~~xml
<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 用来绘制圆角效果。
~~~java
RoundingParams roundingParams = RoundingParams.fromCornersRadius(7f);
roundingParams.setOverlayColor(R.color.green);
// 或用 fromCornersRadii 以及 asCircle 方法
genericDraweeHierarchyBuilder
.setRoundingParams(roundingParams);
~~~
你也可以在运行时,改变圆角效果
~~~java
RoundingParams roundingParams =
mSimpleDraweeView.getHierarchy().getRoundingParams();
roundingParams.setBorder(R.color.red, 1.0);
roundingParams.setRoundAsCircle(true);
mSimpleDraweeView.getHierarchy().setRoundingParams(roundingParams);
~~~
> 在运行时,不能改变呈现方式: 原本是圆角,不能改为圆圈。
### 使用ControllerBuilder
`SimpleDraweeView` 有两个方法可以设置所要加载显示图片,简单的方法就是`setImageURI`。
如果你需要对加载显示的图片做更多的控制和定制,那就需要用到 DraweeController,本页说明如何使用。
#### DraweeController
首先,创建一个DraweeController, 然后传递图片加载请求给 PipelineDraweeControllerBuilder
随后,你可以控制controller的其他选项了:
~~~java
ControllerListener listener = new BaseControllerListener() {...}
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setTapToRetryEnabled(true)
.setOldController(mSimpleDraweeView.getController())
.setControllerListener(listener)
.build();
mSimpleDraweeView.setController(controller);
~~~
在指定一个新的controller的时候,使用`setOldController`,这可节省不必要的内存分配。
#### 自定义图片加载请求
在更进一步的用法中,你需要给Image pipeline 发送一个ImageRequest。下面是一个图片加载后,使用后处理器(postprocessor) 进行图片后处理的例子.
~~~java
Uri uri;
Postprocessor myPostprocessor = new Postprocessor() { ... }
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setPostprocessor(myPostprocessor)
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getController())
// 其他设置
.build();
~~~
### 渐进式JPEG图
*注意: 本页提及的API仅是初步设计,后续可能变动*
Fresco 支持渐进式的网络JPEG图。在开始加载之后,图会从模糊到清晰渐渐呈现。
你可以设置一个清晰度标准,在未达到这个清晰度之前,会一直显示占位图。
渐进式JPEG图仅仅支持网络图。
##### 初始化
[配置Image pipeline时](#) 需要传递一个 ProgressiveJpegConfig 的实例。
这个实例需要完成两个事情:1. 返回下一个需要解码的扫描次数2. 确定多少个扫描次数之后的图片才能开始显示。
下面的实例中,为了实现节省CPU,并不是每个扫描都进行解码。
注意:
- 每次解码完之后,调用`getNextScanNumberToDecode`, 等待扫描值大于返回值,才有可能进行解码。
假设,随着下载的进行,下载完的扫描序列如下: `1, 4, 5, 10`。那么:
1. 首次调用`getNextScanNumberToDecode`返回为2, 因为初始时,解码的扫描数为0。
1. 那么1将不会解码,下载完成4个扫描时,解码一次。下个解码为扫描数为6
1. 5不会解码,10才会解码
~~~java
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
##### At Request Time
目前,我们必须显式地在加载时,允许渐进式JPEG图片加载。
~~~java
Uri uri;
ImageRequest request = ImageRequestBuilder
.newBuilderWithSource(uri)
.setProgressiveRenderingEnabled(true)
.build();
PipelineDraweeController controller = Fresco.newControllerBuilder()
.setImageRequest(requests)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
~~~
我们希望在后续的版本中,在`setImageURI`方法中可以直接支持渐进式图片加载。
### 动画图(gif)
Fresco 支持GIF和WebP 格式图片;支持WebP 格式的动画图也支持(包括扩展WebP 格式),支持2.3及其以后那些没有原生WebP支持的系统。
#### 设置动画图自动播放
如果你希望图片下载完之后自动播放,同时,当View从屏幕移除时,停止播放,只需要在[image request](#) 中简单设置,如下:
~~~java
Uri uri;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setAutoPlayAnimation(true)
. // other setters
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
. // other setters
.build();
mSimpleDraweeView.setController(controller);
~~~
#### 手动控制动画图播放
也许,你希望在图片加载完之后,手动控制动画的播放,那么这样做:
~~~java
ControllerListener controllerListener = new BaseControllerListener() {
@Override
public void onFinalImageSet(
String id,
@Nullable ImageInfo imageInfo,
@Nullable Animatable anim) {
if (anim != null) {
// 根据业务逻辑,在合适的时机播放动画。
}
};
Uri uri;
PipelineDraweeController controller = Fresco.newControllerBuilder()
.setControllerListener(controllerListener)
.setUri(uri);
// other setters
.build();
mSimpleDraweeView.setController(controller);
~~~
另外,controller提供对[Animatable](http://developer.android.com/reference/android/graphics/drawable/Animatable.html) 的访问。
如果有可用动画的话,可对动画进行灵活的控制:
~~~java
Animatable animation = mSimpleDraweeView.getController().getAnimatable();
if (animation != null) {
// 开始播放
animation.start();
// 一段时间之后,根据业务逻辑,停止播放
animation.stop();
}
~~~
### 多图请求及图片复用
多图请求需 [自定义ImageRequest](#).
#### 先显示低分辨率的图,然后是高分辨率的图
如果你要显示一张高分辨率的图,但是这张图下载比较耗时。你可以在下载前,先提供一张很快能下载完的小缩略图。这比一直显示占位图,用户体验会好很多。
这时,你可以设置两个图片的URI,一个是低分辨率的缩略图,一个是高分辨率的图。
~~~java
Uri lowResUri, highResUri;
PipelineDraweeController controller = Fresco.newControllerBuilder()
.setLowResImageRequest(ImageRequest.fromUri(lowResUri))
.setImageRequest(ImageRequest.fromUri(highResUri))
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
~~~
#### 缩略图预览
*本功能仅支持本地URI,并且是JPEG图片格式*
如果本地JPEG图,有EXIF的缩略图,image pipeline 会立刻返回一个缩略图。完整的清晰大图,在decode完之后再显示。
~~~java
Uri uri;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setLocalThumbnailPreviewsEnabled(true)
.build();
PipelineDraweeController controller = Fresco.newControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
~~~
#### 本地图片复用
大部分的时候,一个图片可能会对应有多个URI,比如:
- 拍照上传。本地图片较大,上传的图片较小。上传完成之后的图片,有一个url,如果要加载这个url,可直接加载本地图片。
- 本地已经有600x600尺寸的大图了,需要显示100x100的小图
对于一个URI,image pipeline 会依次检查内存,磁盘,如果没有从网络下载。
而对于一个图片的多个URI,image pipeline 会先检查他们是否在内存中。如果没有任何一个是在内存中的,会检查是否在本地存储中。如果也没有,才会执行网络下载。
但凡有任何一个检查发现在内存或者在本地存储中,都会进行复用。列表顺序就是要显示的图片的优先顺序。
使用时,创建一个image request 列表,然后传给ControllerBuilder:
~~~java
Uri uri1, uri2;
ImageRequest request = ImageRequest.fromUri(uri1);
ImageRequest request2 = ImageRequest.fromUri(uri2);
ImageRequest[] requests = { request1, request2 };
PipelineDraweeController controller = Fresco.newControllerBuilder()
.setFirstAvailableImageRequests(requests)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
~~~
### 监听下载事件
你也许想在图片下载完成或者下载失败之后,做一些其他事情。
图片是后台线程异步加载的,我们可以使用一个`ControllerListener`实现事件的监听。
_在监听事件回调时,无法修改图片,如果需要修改图片,可使用[后处理器(Postprocessor)](#)
~~~ ControllerListener controllerListener = new BaseControllerListener() {
@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](#)。
### 缩放图片
#### 什么时候该修改图片尺寸
一般地,当所要显示的图片和显示区域大小不一致时,会按以下方式进行处理。
1. 从服务器下载小一些的图片
1. 显示时缩放图片
1. 调整图片尺寸大小
对于一个图片,如果服务器支持不同尺寸的缩略图,那么每次下载都选择尺寸最匹配的图片,这个不仅节省数据流量也节约本地储存和CPU。
如果服务器不支持,或者处理本地图片的话,第二个选择是[使用缩放类型](#)。缩放是用Androi内置的功能使图像和显示边界相符。在4.0之后,支持硬件加速。这在大部分情况下是最快,同时也是最高效的显示一张和显示边界大小相符的图片的方式。首先指定`layout_width`和`layout_width`为指定值,然后指定[缩放类型](#)
但当所要显示的图片比显示区域大许多的时候,不推荐这样做,缩放过程会导致大量的内存消耗。
这时,需要改变图片尺寸。
#### 修改图片尺寸
调整大小并不是修改原来的文件,而是在解码之前,在native内存中修改。
这个缩放方法,比Android内置的缩放范围更大。Android相机生成的照片一般尺寸都很大,需要调整大小之后才能被显示。
目前,仅仅支持JPEG格式的图片,同时,大部分的Android系统相机图片都是JPEG的。
如果要修改图片尺寸,创建`ImageRequest`时,提供一个 ResizeOptions:
~~~java
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);
~~~
### 自动旋转
如果看到的图片是侧着的,用户是难受的。许多设备会在JPEG文件的metadata中记录下照片的方向。如果你想图片呈现的方向和设备屏幕的方向一致,你可以简单地这样做到:
~~~java
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setAutoRotateEnabled(true)
.build();
// as above
~~~
### 修改图片
有时,我们想对从服务器下载,或者本地的图片做些修改,比如在某个坐标统一加个网格什么的。这时使用后处理器(Postprocessor)便可达到目的。
##### 例子:
给图片加个网格:
~~~java
Uri uri;
Postprocessor redMeshPostprocessor = new Postprocessor() {
@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 = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getOldController())
// other setters as you need
.build();
mSimpleDraweeView.setController(controller);
~~~
##### 注意点
图片在进入后处理器(postprocessor)的图片是原图的一个完整拷贝,原来的图片不受修改的影响。在5.0以前的机器上,拷贝后的图片也在native内存中。
在开始一个图片显示时,即使是反复显示同一个图片,在每次进行显示时,都需要指定后处理器。
对于同一个图片,每次显示,可以使用不同的后处理器。
##### Repeated Postprocessors
如果想对同一个图片进行多次后处理,那么继承 BaseRepeatedPostprocessor 即可。该类有一个`update`方法,需要执行后处理时,调用该方法即可。
下面的例子展示了在运行时,后处理改变图片网格的颜色:
~~~java
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`,但是这个后处理器是状态相关了。
### 图片请求
如果你需要的`ImageRequest`仅仅是一个URI,那么`ImageRequest.fromURI`就足够了,在[多图请求及图片复用](#)中,有这样的用法。
否则,你需要`ImageRequestBuilder`来做更多的事情。
~~~java
Uri uri;
ImageDecodeOptions decodeOptions = ImageDecodeOptions.newBuilder()
.setBackgroundColor(Color.GREEN)
.build();
ImageRequest request = ImageRequestBuilder
.newBuilderWithSource(uri)
.setAutoRotateEnabled(true)
.setLocalThumbnailPreviewsEnabled(true)
.setLowestPermittedRequestLevel(RequestLevel.FULL_FETCH)
.setProgressiveRenderingEnabled(false)
.setResizeOptions(new ResizeOptions(width, height))
.build();
~~~
##### ImageRequest 的属性和成员
- `uri` - 唯一的必选的成员. 参考 [支持的URIs](#)
- `autoRotateEnabled` - 是否支持[自动旋转](#).
- `progressiveEnabled` - 是否支持[渐进式加载](#).
- `postprocessor` - [后处理器(postprocess)](#).
- `resizeOptions` - 图片缩放选项,用前请先阅读[缩放和旋转](#).
##### 最低请求级别
Image pipeline 加载图片时有一套明确的[请求流程](#)
1. 检查内存缓存,有如,立刻返回。这个操作是实时的。
1. 检查未解码的图片缓存,如有,解码并返回。
1. 检查磁盘缓存,如果有加载,解码,返回。
1. 下载或者加载本地文件。调整大小和旋转(如有),解码并返回。对于网络图来说,这一套流程下来是最耗时的。
`setLowestPermittedRequestLevel`允许设置一个最低请求级别,请求级别和上面对应地有以下几个取值:
- `BITMAP_MEMORY_CACHE`
- `ENCODED_MEMORY_CACHE`
- `DISK_CACHE`
- `FULL_FETCH`
如果你需要立即取到一个图片,或者在相对比较短时间内取到图片,否则就不显示的情况下,这非常有用。
### 自定义View
#### DraweeHolders
总有一些时候,`DraweeViews`是满足不了需求的,在展示图片的时候,我们还需要展示一些其他的内容,或者支持一些其他的操作。在同一个View里,我们可能会想显示一张或者多张图。
在自定义View中,Fresco 提供了两个类来负责图片的展现:
- `DraweeHolder` 单图情况下用。
- `MultiDraweeHolder` 多图情况下用。
#### 自定义View需要完成的事情
Android 呈现View对象,只有View对象才能得到一些系统事件的通知。`DraweeViews`处理这些事件通知,高效地管理内存。使用`DraweeHolder`时,你需要自己实现这几个方法。
##### 处理 attach/detach 事件
**如果没按照以下步骤实现的话,很可能会引起内存泄露**
当图片不再在View上显示时,比如滑动时View滑动到屏幕外,或者不再绘制,图片就不应该再存在在内存中。Drawees 监听这些事情,并负责释放内存。当图片又需要显示时,重新加载。
这些在`DraweeView`中是自动的,但是在自定义View中,需要我们自己去操作,如下:
~~~java
DraweeHolder mDraweeHolder;
@Override
public void onDetachedFromWindow() {
super.onDetachedToWindow();
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中,需要这样:
~~~java
@Override
public boolean onTouchEvent(MotionEvent event) {
return mDraweeHolder.onTouchEvent(event) || super.onTouchEvent(event);
}
~~~
##### 自定义onDraw
~~~java
Drawable drawable = mDraweeHolder.getHierarchy().getTopLevelDrawable();
drawable.setBounds(...);
~~~
否则图片将不会出现
- 不要向下转换这个Drawable
- 不要变换这个Drawable
##### 其他应该做的
- 重写 `verifyDrawable:`
~~~java
@Override
protected boolean verifyDrawable(Drawable who) {
if (who == mDraweeHolder.getHierarchy().getTopLevelDrawable()) {
return true;
}
// 对其他Drawable的验证逻辑
}
~~~
- 确保`invalidateDrawable` 处理了图片占用的那块区域。
#### 创建 DraweeHolder
这同样需要非常小心和细致
##### 构造函数
我们推荐如下实现构造函数:
- 重写3个构造函数
- 在每个构造函数中调用同等签名的父类构造函数,和一个私有的`init`方法。
- 在`init`方法中执行初始化操作。
即,不要在构造函数中用`this`来调用另外一个构造。
这样可以保证,不管调用哪个构造,都可以正确地执行初始化流程。然后在`init`方法中创建holder。
##### 创建 Holder
如果有可能,只在View创建时,创建Drawees。创建DraweeHierarchy开销较大,最好只做一次。
~~~java
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。
~~~java
DraweeController controller = Fresco.newControllerBuilder()
.setUri(uri)
.setOldController(mDraweeHolder.getController())
.build();
mDraweeHolder.setController(controller);
~~~
#### MultiDraweeHolder
和`DraweeHolder`相比,`MultiDraweeHolder`有 `add`, `remove`, `clear`等方法可以操作Drawees。如下:
~~~java
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`那样。
### 一些陷阱
##### 不要向下转换
不要试图把Fresco返回的一些对象进行向下转化,这也许会带来一些对象操作上的便利,但是也许在后续的版本中,你会遇到一些因为向下转换特性丢失导致的难以处理的问题。
##### 不要使用getTopLevelDrawable
`DraweeHierarchy.getTopLevelDrawable()`**仅仅** 应该在DraweeViews中用,除了定义View中,其他应用代码建议连碰都不要碰这个。
在自定义View中,也千万不要将返回值向下转换,也许下个版本,我们会更改这个返回值类型。
##### 不要复用 DraweeHierarchies
永远不要吧`DraweeHierarchy` 通过 `DraweeView.setHierarchy` 设置给不同的View。DraweeHierarchy是由一系列Drawable组成的。在Android中, Drawable不能被多个View共享。
##### 不要在多个DraweeHierarchy中使用同一个Drawable
原因同上。当时可以使用不同的资源ID。Android实际会创建不同的Drawable。
##### 不要直接给 `DraweeView` 设置图片。
目前 `DraweeView` 直接继承于ImageView,因此它有 `setImageBitmap`,`setImageDrawable` 等方法。
如果利用这些方法,直接设置一个图片。内部的`DraweeHierarchy`就会丢失,也就无法取到imagepipeline 的任何图像了。
##### 使用DraweeView时,请不要使用任何ImageView的属性
在后续的版本中,DraweeView会直接从View派生。任何属于ImageView但是不属于View的方法都会被移除。