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