[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的方法都会被移除。