1:簡介
Fresco是Facebook最新推出的一款用于Android應用中展示圖檔的強大圖檔庫,可以從網絡、本地存儲和本地資源中加載圖檔。相對于ImageLoader,擁有更快的圖檔下載下傳速度以及可以加載和顯示gif圖等諸多優勢,是個很好的圖檔架構。
2:特點
2.1:記憶體管理
在5.0以下系統,Fresco将圖檔放到一個特别的記憶體區域。當然,在圖檔不顯示的時候,占用的記憶體會自動被釋放。這會使得APP更加流暢,減少因圖檔記憶體占用而引發的OOM。
記憶體配置設定采用:系統匿名共享記憶體
2.2:漸進式呈現圖檔
漸進式圖檔格式先呈現大緻的圖檔輪廓,然後随着圖檔下載下傳的繼續,呈現逐漸清晰的圖檔,這對于移動裝置,尤其是慢網絡有極大的利好,可帶來更好的使用者體驗。
2.3:支援加載Gif圖,支援WebP格式
2.4:圖像的呈現
- 自定義居中焦點(對人臉等圖檔顯示非常有幫助)。
- 圓角圖,當然圓圈也行。
- 下載下傳失敗之後,點選重新下載下傳。
- 自定義占位圖,自定義overlay, 或者進度條。
- 指定使用者按壓時的overlay。
2.5:圖像的加載
- 為同一個圖檔指定不同的遠端路徑,或者使用已經存在本地緩存中的圖檔。
- 先顯示一個低解析度的圖檔,等高清圖下載下傳完之後再顯示高清圖。
- 加載完成回調通知。
- 對于本地圖,如有EXIF縮略圖,在大圖加載完成之前,可先顯示縮略圖。
- 縮放或者旋轉圖檔。
- 處理已下載下傳的圖檔。
3:下載下傳位址
Github下載下傳位址是:https://github.com/facebook/fresco
官方使用網址:http://fresco-cn.org/docs/index.html
中文文檔位址:https://www.fresco-cn.org/docs/index.html
4:支援的URI
類型 | Scheme | 示例 |
---|---|---|
遠端圖檔 | http://, https:// | HttpURLConnection |
本地檔案 | file:// | FileInputStream |
Content provider | content:// | ContentResolver |
asset目錄下的資源 | asset:// | AssetManager |
res目錄下的資源 | res:// | Resources.openRawResource |
需要注意的是:Fresco中所有的URI都必須是絕對路徑,并且帶上該URI的scheme
例如,要顯示res/drawable下的一張圖檔可以使用下面的方式:
上面的額”aaaaa”可以使用随意的字元串,不過還是建議使用包名來書寫。
5:常用API
1:寬度不支援wrap_content, 如果要設定寬高比, 需要在Java代碼中指定setAspectRatio(float ratio);
2:高度不支援wrap_content
3:下載下傳成功之前顯示的圖檔
4:設定圖檔縮放. 通常使用focusCrop,該屬性值會通過算法把人頭像放在中間
5:加載失敗的時候顯示的圖檔
6:加載失敗的時候圖檔的縮放類型
7:加載失敗,提示使用者點選重新加載的圖檔(會覆寫failureImage的圖檔)
8:設定圓形方式顯示圖檔
9:圓角設定
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"
6:使用步驟
6.1:添加依賴
dependencies {
// 在 API < 14 上的機器支援 WebP 時,需要添加
compile 'com.facebook.fresco:animated-base-support:0.14.1'
// 支援 GIF 動圖,需要添加
compile 'com.facebook.fresco:animated-gif:0.14.1'
// 支援 WebP (靜态圖+動圖),需要添加
compile 'com.facebook.fresco:animated-webp:0.14.1'
compile 'com.facebook.fresco:webpsupport:0.14.1'
// 僅支援 WebP 靜态圖,需要添加
compile 'com.facebook.fresco:webpsupport:0.14.1'
// 其他依賴
compile 'com.facebook.fresco:fresco:0.14.1'
}
fresco最新版本是:1.0.0
6.2:在application中初始化Fresco
Fresco.initialize(this);
6.3:配置網絡權限
6.4:在xml布局檔案中,加入命名空間
<!-- 其他元素-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent">
6.5:在xml中引入SimpleDraweeView
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="130dp"
android:layout_height="130dp"
fresco:placeholderImage="@drawable/my_drawable"
/>
6.6:在Java代碼中開始加載圖檔
Uri uri = Uri.parse("hhttp://p1.so.qhmsg.com/bdr/_240_/t01ffd622bffeabb5e1.jpg");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);
6.7:注意事項
如果項目中使用了OkHttp需要進行替換,
For OkHttp2:
compile "com.facebook.fresco:imagepipeline-okhttp:0.12.0+"
For OkHttp3:
compile "com.facebook.fresco:imagepipeline-okhttp3:0.12.0+"
7:例子
7.1:帶進度條的圖檔
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_fresco_spimg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.bruce.chang.testfresco.FrescoSpimgActivity">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/sdv_fresco"
android:layout_width="150dp"
android:layout_height="150dp"
fresco:placeholderImage="@mipmap/fbb"
/>
<Button
android:id="@+id/bt_load"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="加載"
/>
</LinearLayout>
代碼
setTitle("帶進度條的圖檔");
bt_load.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 設定帶進度條樣式
GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(getResources());
GenericDraweeHierarchy hierarchy = builder.setProgressBarImage(new ProgressBarDrawable()).build();
sdv_fresco.setHierarchy(hierarchy);
Uri uri = Uri.parse("http://bizhi.zhuoku.com/bizhi/200706/4/20070625/bingbing/012.jpg");
// 設定顯示圖檔
sdv_fresco.setImageURI(uri);
}
});
結果,因為布局中設定了placeholderImage,是以會加載一張預設圖檔
7.2:圖檔的不同裁剪
布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/sdv_fresco_crop"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginTop="48dp"
android:background="@android:color/black"
fresco:placeholderImage="@mipmap/fbb" />
<!--裁剪方式的描述資訊-->
<TextView
android:id="@+id/tv_fresco_explain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:textSize="16sp" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:orientation="vertical">
<!--9個代表不同裁剪樣式的button,這裡省略-->
</LinearLayout>
</ScrollView>
</LinearLayout>
代碼設定
- GenericDraweeHierarchyBuilder的初始化
- imageDisplay(加載圖檔的方法)
//顯示圖檔
private void (GenericDraweeHierarchy hierarchy) {
sdv_fresco_crop.setHierarchy(hierarchy);
Uri uri = Uri.parse("http://bizhi.zhuoku.com/bizhi/200706/4/20070625/bingbing/012.jpg");
sdv_fresco_crop.setImageURI(uri);
}
- 9個按鈕的方法
@Override
public void onClick(View view) {
switch (view.getId()) {
//CENTER
case R.id.bt_fresco_center:
tv_fresco_explain.setText("居中,無縮放");
//樣式設定
GenericDraweeHierarchy hierarchy = builder.setActualImageScaleType(ScalingUtils.ScaleType.CENTER).build();
//顯示圖檔
imageDisplay(hierarchy);
break;
//CENTER_CROP
case R.id.bt_fresco_centercrop:
tv_fresco_explain.setText("保持寬高比縮小或放大,使得兩邊都大于或等于顯示邊界。居中顯示");
//樣式設定
GenericDraweeHierarchy hierarchy1 = builder.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP).build();
//顯示圖檔
imageDisplay(hierarchy1);
break;
//FOCUS_CROP
case R.id.bt_fresco_focuscrop:
tv_fresco_explain.setText("同centerCrop, 但居中點不是中點,而是指定的某個點,這裡我設定為圖檔的左上角那點");
//設定focusCrop的縮放形式 并指定縮放的中心點在左上角
PointF point = new PointF(f, f);
//樣式設定
GenericDraweeHierarchy hierarchy2 = builder.setActualImageScaleType(ScalingUtils.ScaleType.FOCUS_CROP)
.setActualImageFocusPoint(point)
.build();
//顯示圖檔
imageDisplay(hierarchy2);
break;
//CENTER_INSIDE
case R.id.bt_fresco_centerinside:
tv_fresco_explain.setText("使兩邊都在顯示邊界内,居中顯示。如果圖尺寸大于顯示邊界,則保持長寬比縮小圖檔");
GenericDraweeHierarchy hierarchy3 = builder.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_INSIDE).build();
imageDisplay(hierarchy3);
break;
//FIT_CENTER
case R.id.bt_fresco_fitcenter:
tv_fresco_explain.setText("保持寬高比,縮小或者放大,使得圖檔完全顯示在顯示邊界内。居中顯示");
GenericDraweeHierarchy hierarchy4 = builder.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER).build();
imageDisplay(hierarchy4);
break;
//FIT_START
case R.id.bt_fresco_fitstart:
tv_fresco_explain.setText("保持寬高比,縮小或者放大,使得圖檔完全顯示在顯示邊界内,不居中,和顯示邊界左上對齊");
GenericDraweeHierarchy hierarchy5 = builder.setActualImageScaleType(ScalingUtils.ScaleType.FIT_START).build();
imageDisplay(hierarchy5);
break;
//FIT_END
case R.id.bt_fresco_fitend:
tv_fresco_explain.setText("保持寬高比,縮小或者放大,使得圖檔完全顯示在顯示邊界内,不居中,和顯示邊界右下對齊");
GenericDraweeHierarchy hierarchy6 = builder.setActualImageScaleType(ScalingUtils.ScaleType.FIT_END).build();
imageDisplay(hierarchy6);
break;
//FIT_XY
case R.id.bt_fresco_fitxy:
tv_fresco_explain.setText("不保持寬高比,填充滿顯示邊界");
GenericDraweeHierarchy hierarchy7 = builder.setActualImageScaleType(ScalingUtils.ScaleType.FIT_XY).build();
imageDisplay(hierarchy7);
break;
//null
case R.id.bt_fresco_none:
tv_fresco_explain.setText("不設定任何樣式");
GenericDraweeHierarchy hierarchy8 = builder.setActualImageScaleType(null).build();
imageDisplay(hierarchy8);
break;
default:
break;
}
}
結果,因為圖檔大小的緣故,沒有完全示範,自己可以動手試試
7.3:圓形和圓角圖檔
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/sdv_fresco_circleandcorner"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_gravity="center"
fresco:placeholderImage="@mipmap/fbb" />
<Button
android:id="@+id/bt_fresco_circle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="設定圓形圖檔" />
<Button
android:id="@+id/bt_fresco_corner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="設定圓角圖檔" />
</LinearLayout>
代碼
uri = Uri.parse("http://bizhi.zhuoku.com/bizhi/200706/4/20070625/bingbing/012.jpg");
builder = new GenericDraweeHierarchyBuilder(getResources());
bt_fresco_circle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 參數設定為圓形
RoundingParams params = RoundingParams.asCircle();
GenericDraweeHierarchy hierarchy = builder.setRoundingParams(params).build();
sdv_fresco_circleandcorner.setHierarchy(hierarchy);
sdv_fresco_circleandcorner.setImageURI(uri);
}
});
bt_fresco_corner.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 配置參數
RoundingParams params = RoundingParams.fromCornersRadius(f);//設定圓角大小
params.setOverlayColor(getResources().getColor(android.R.color.holo_blue_light));//覆寫層
params.setBorder(getResources().getColor(android.R.color.holo_blue_light), );//邊框
// params.setRoundAsCircle(true);//如果是RoundingParams.fromCornersRadius,這個可以強制進行圓形展示
// 設定圓形參數
GenericDraweeHierarchy hierarchy = builder.setRoundingParams(params).build();
sdv_fresco_circleandcorner.setHierarchy(hierarchy);
// 加載圖檔
sdv_fresco_circleandcorner.setImageURI(uri);
}
});
結果
7.4:漸進式顯示圖檔
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_fresco_jpeg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.facebook.drawee.view.SimpleDraweeView
android:layout_gravity="center"
android:id="@+id/sdv_fresco_jpeg"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginTop="48dp"
fresco:placeholderImage="@mipmap/fbb" />
<Button
android:id="@+id/sdv_fresco_askImg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="請求網絡圖檔" />
</LinearLayout>
代碼
builder = new GenericDraweeHierarchyBuilder(getResources());
sdv_fresco_askImg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 加載品質配置
ProgressiveJpegConfig jpegConfig = new ProgressiveJpegConfig() {
@Override
public int getNextScanNumberToDecode(int scanNumber) {
return scanNumber + ;
}
@Override
public QualityInfo getQualityInfo(int scanNumber) {
boolean isGoodEnough = (scanNumber >= );
return ImmutableQualityInfo.of(scanNumber, isGoodEnough, false);
}
};
ImagePipelineConfig.newBuilder(FrescoJianJinShiActivity.this).setProgressiveJpegConfig(jpegConfig).build();
// 擷取圖檔URL
uri = Uri.parse("http://bizhi.zhuoku.com/bizhi/200706/4/20070625/bingbing/012.jpg");
// 擷取圖檔請求
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri).setProgressiveRenderingEnabled(true).build();
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setTapToRetryEnabled(true)
.setOldController(sdv_fresco_jpeg.getController())//使用oldController可以節省不必要的記憶體配置設定
.build();
// 設定加載的控制
sdv_fresco_jpeg.setController(draweeController);
}
});
結果
7.5:Gif動畫圖檔
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_fresco_gif"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/sdv_fresco_gif"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginTop="48dp"
android:layout_gravity="center"
fresco:placeholderImage="@mipmap/fbb" />
<Button
android:id="@+id/bt_fresco_askImg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="請求gif圖檔" />
<Button
android:id="@+id/bt_fresco_stopAnim"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="動畫停止" />
<Button
android:id="@+id/bt_fresco_startAnim"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="動畫開始" />
</LinearLayout>
代碼
- 請求Gif圖檔
bt_fresco_askImg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 圖檔位址
Uri uri = Uri.parse("http://www.sznews.com/humor/attachement/gif/site3/20140902/4487fcd7fc66156f51db5d.gif");
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setAutoPlayAnimations(false)//設定為true将循環播放Gif動畫
.setOldController(sdv_fresco_gif.getController())
.build();
// 設定控制器
sdv_fresco_gif.setController(controller);
}
});
- 開始動畫
bt_fresco_startAnim.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Animatable animation = sdv_fresco_gif.getController().getAnimatable();
if (animation != null && !animation.isRunning()) {
animation.start();
}
}
});
- 停止動畫
bt_fresco_stopAnim.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Animatable animation = sdv_fresco_gif.getController().getAnimatable();
if (animation != null && animation.isRunning()) {
animation.stop();
}
}
});
結果(不要忘了在gradle中添加gif的依賴)
7.6:多圖請求以及圖檔複用
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_fresco_multi"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/sdv_fresco_multi"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_gravity="center"
android:layout_marginTop="48dp"
fresco:placeholderImage="@mipmap/fbb" />
<Button
android:id="@+id/bt_fresco_multiImg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="先顯示低分辨率的圖,然後是高分辨率的圖"
android:onClick="bt_fresco_multiImg_click"
/>
<Button
android:id="@+id/bt_fresco_thumbnailImg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="本地縮略圖預覽"
android:onClick="bt_fresco_thumbnailImg_click"
/>
<Button
android:id="@+id/bt_fresco_multiplexImg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="本地圖檔複用"
android:onClick="bt_fresco_multiplexImg_click"
/>
</LinearLayout>
代碼
- 先顯示低分辨率的圖,然後是高分辨率的圖
void bt_fresco_multiImg_click(View view) {
//同一張圖檔,不同品質的兩個uri
Uri lowResUri = Uri.parse("http://img1.gamedog.cn/2012/03/11/19-120311133617-50.jpg");
Uri highResUri = Uri.parse("http://img5.duitang.com/uploads/item/201312/03/20131203153823_Y4y8F.jpeg");
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setLowResImageRequest(ImageRequest.fromUri(lowResUri))
.setImageRequest(ImageRequest.fromUri(highResUri))
.setOldController(sdv_fresco_multi.getController())
.build();
sdv_fresco_multi.setController(controller);
}
- 本地縮略圖預覽
void bt_fresco_thumbnailImg_click(View view){
//将本地圖檔位址轉換成Uri
Uri uri = Uri.fromFile(new File(Environment.getExternalStorageDirectory()+"/swj.jpg"));
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setLocalThumbnailPreviewsEnabled(true)
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(sdv_fresco_multi.getController())
.build();
sdv_fresco_multi.setController(controller);
}
- 本地圖檔複用
void bt_fresco_multiplexImg_click(View view){
//本地圖檔的複用
//在請求之前,還會去記憶體中請求一次圖檔,沒有才會先去本地,最後去網絡uri
//本地準備複用圖檔的uri 如果本地這個圖檔不存在,會自動去加載下一個uri
Uri uri1 = Uri.fromFile(new File(Environment.getExternalStorageDirectory()+"/swj.jpg"));
//圖檔的網絡uri
Uri uri2 = Uri.parse("http://img5.duitang.com/uploads/item/201312/03/20131203153823_Y4y8F.jpeg");
ImageRequest request1 = ImageRequest.fromUri(uri1);
ImageRequest request2 = ImageRequest.fromUri(uri2);
ImageRequest[] requests = {request1, request2};
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setFirstAvailableImageRequests(requests)
.setOldController(sdv_fresco_multi.getController())
.build();
sdv_fresco_multi.setController(controller);
}
結果
7.7:圖檔加載監聽
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_fresco_listener"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.facebook.drawee.view.SimpleDraweeView
android:layout_gravity="center"
android:id="@+id/sdv_fresco_listener"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginTop="48dp"
fresco:placeholderImage="@mipmap/fbb" />
<Button
android:id="@+id/bt_fresco_listener"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="開始加載圖檔"
android:onClick="bt_fresco_listener_click"
/>
<TextView
android:id="@+id/tv_fresco_listener"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="14sp"
android:text="tv_fresco_listener"
/>
<TextView
android:id="@+id/tv_fresco_listener2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="14sp"
android:text="tv_fresco_listener2"
/>
</LinearLayout>
代碼
- 自定義BaseControllerListener初始化
//對所有的圖檔加載,onFinalImageSet 或者 onFailure 都會被觸發。前者在成功時,後者在失敗時。
//如果允許呈現漸進式JPEG,同時圖檔也是漸進式圖檔,onIntermediateImageSet會在每個掃描被解碼後回調。
// 具體圖檔的那個掃描會被解碼,參見漸進式JPEG圖
private ControllerListener controllerListener = new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
//前者在成功時
super.onFinalImageSet(id, imageInfo, animatable);
if (imageInfo == null) {
return;
}
QualityInfo qualityInfo = imageInfo.getQualityInfo();
tv_fresco_listener.setText("Final image received! " +
"\nSize: " + imageInfo.getWidth()
+ "x" + imageInfo.getHeight()
+ "\nQuality level: " + qualityInfo.getQuality()
+ "\ngood enough: " + qualityInfo.isOfGoodEnoughQuality()
+ "\nfull quality: " + qualityInfo.isOfFullQuality());
}
@Override
public void onIntermediateImageSet(String id, ImageInfo imageInfo) {
super.onIntermediateImageSet(id, imageInfo);
tv_fresco_listener2.setText("IntermediateImageSet image receiced");
}
//後者在失敗時
@Override
public void onFailure(String id, Throwable throwable) {
super.onFailure(id, throwable);
tv_fresco_listener.setText("Error loading" + id);
}
};
- 開始加載圖檔按鈕方法
void bt_fresco_listener_click(View view) {
ProgressiveJpegConfig jpegConfig = new ProgressiveJpegConfig() {
@Override
public int getNextScanNumberToDecode(int scanNumber) {
return scanNumber + ;
}
@Override
public QualityInfo getQualityInfo(int scanNumber) {
boolean isGoodEnough = (scanNumber >= );
return ImmutableQualityInfo.of(scanNumber, isGoodEnough, false);
}
};
ImagePipelineConfig.newBuilder(this).setProgressiveJpegConfig(jpegConfig).build();
Uri uri = Uri.parse("http://h.hiphotos.baidu.com/zhidao/pic/item/58ee3d6d55fbb2fbac4f2af24f4a20a44723dcee.jpg");
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri).setProgressiveRenderingEnabled(true).build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setOldController(sdv_fresco_listener.getController())
.setImageRequest(request)
//設定監聽器監聽圖檔加載狀态
.setControllerListener(controllerListener)
.build();
sdv_fresco_listener.setController(controller);
}
結果,加載成功的時候可以得到圖檔的相關資訊,如Size,在浏覽器中檢視,該圖檔的尺寸就是那麼大的。
7.8:圖檔縮放和旋轉
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_fresco_resize"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/sdv_fresco_resize"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_gravity="center"
android:layout_marginTop="48dp"
fresco:placeholderImage="@mipmap/fbb" />
<Button
android:id="@+id/bt_fresco_resize"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:onClick="bt_fresco_resize_click"
android:text="修記憶體中改圖檔大小" />
<Button
android:id="@+id/bt_fresco_rotate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="bt_fresco_rotate_click"
android:text="旋轉圖檔" />
</LinearLayout>
代碼
//縮放
void bt_fresco_resize_click(View view) {
int width = ;
int height = ;
Uri uri = Uri.parse("http://c.hiphotos.baidu.com/image/pic/item/962bd40735fae6cd21a519680db30f2442a70fa1.jpg");
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setResizeOptions(new ResizeOptions(width, height)).build();
PipelineDraweeController controller = (PipelineDraweeController) Fresco.newDraweeControllerBuilder()
.setOldController(sdv_fresco_resize.getController())
.setImageRequest(request)
.build();
sdv_fresco_resize.setController(controller);
}
//旋轉
void bt_fresco_rotate_click(View view) {
Uri uri2 = Uri.parse("http://c.hiphotos.baidu.com/image/pic/item/962bd40735fae6cd21a519680db30f2442a70fa1.jpg");
RotationOptions rotationOptions = RotationOptions.forceRotation(RotationOptions.ROTATE_90);
ImageRequest request1 = ImageRequestBuilder.newBuilderWithSource(uri2)
.setRotationOptions(RotationOptions.autoRotate())
.setRotationOptions(rotationOptions)//旋轉的時候要使用RotationOptions類
.build();//但貌似Fresco在旋轉的功能上不是很好
DraweeController controller1 = Fresco.newDraweeControllerBuilder()
.setImageRequest(request1)
.setOldController(sdv_fresco_resize.getController()).build();
sdv_fresco_resize.setController(controller1);
}
結果
7.9:修改圖檔
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_fresco_modify"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/sdv_fresco_modify"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_gravity="center"
android:layout_marginTop="48dp"
fresco:placeholderImage="@mipmap/fbb" />
<Button
android:id="@+id/bt_fresco_modify"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="為圖檔添加網格"
android:onClick="bt_fresco_modify_click"
/>
</LinearLayout>
代碼
void bt_fresco_modify_click(View view){
Uri uri = Uri.parse("http://c.hiphotos.baidu.com/image/pic/item/962bd40735fae6cd21a519680db30f2442a70fa1.jpg");
Postprocessor redMeshPostprocessor = new BasePostprocessor() {
@Override
public String getName() {
return "redMeshPostprocessor";
}
//繪制紅色點狀網格
@Override
public void process(Bitmap bitmap) {
for (int x = ; x < bitmap.getWidth(); x += ) {
for (int y = ; y < bitmap.getHeight(); y += ) {
bitmap.setPixel(x, y, Color.RED);
}
}
}
};
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setPostprocessor(redMeshPostprocessor)
.build();
PipelineDraweeController controller = (PipelineDraweeController)
Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(sdv_fresco_modify.getController())
.build();
sdv_fresco_modify.setController(controller);
}
結果
7.10:
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_fresco_auto_size"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/bt_fresco_loadsmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="加載圖檔"
android:onClick="bt_fresco_loadsmall_click"
/>
<LinearLayout
android:id="@+id/ll_fresco"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>
代碼
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fresco_dynamic_display_img);
ll_fresco = (LinearLayout) findViewById(R.id.ll_fresco);
setTitle("動态展示圖檔");
simpleDraweeView = new SimpleDraweeView(this);
//設定寬高比
simpleDraweeView.setAspectRatio(f);
}
void bt_fresco_loadsmall_click(View view) {
final Uri uri = Uri.parse("http://img4q.duitang.com/uploads/item/201304/27/20130427043538_wAfHC.jpeg");
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.build();
PipelineDraweeController controller = (PipelineDraweeController)
Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(simpleDraweeView.getController())
.build();
simpleDraweeView.setController(controller);
ll_fresco.addView(simpleDraweeView);
}
結果
8:注意事項
8.1:問題處理
- 重複的邊界
這是使用圓角的一個缺陷。 參考圓角和圓圈 來擷取更多資訊。
- 圖檔沒有加載
你可以從 image pipeline 打出的日志來檢視原因。 這裡提供一些通常會導緻問題的原因:
- 檔案不可用
無效的路徑、連結會導緻這種情況。
判斷網絡連結是否有效,你可以嘗試在浏覽器中打開它,看看是否圖檔會被加載。若圖檔依然加載不出來,那麼這不是Fresco的問題。
判斷本地檔案是否有效,你可以通過下面這段代碼來校驗:
FileInputStream fis = new FileInputStream(new File(localUri.getPath()));
如果這裡抛出了異常,那麼這不是Fresco的問題,可能是你的其他代碼導緻的。有可能是沒有擷取到SD卡讀取權限、路徑不合法、檔案不存在等。
- OOM - 無法配置設定圖檔空間
加載特别特别大的圖檔時最容易導緻這種情況。
如果你加載的圖檔比承載的View明顯大出太多,那你應該考慮将它Resize一下。
- Bitmap太大導緻無法繪制
Android 無法繪制長或寬大于2048像素的圖檔。
這是由OpenGL渲染系統限制的,如果它超過了這個界限,Fresco會對它進行Resize。
- 通過Logcat來判斷原因
在加載圖檔時會出現各種各樣的原因導緻加載失敗。
在使用Fresco的時候,最直接的方式就是檢視 image pipeline 打出的VERBOSE級别日志。
- 啟動日志
預設情況下Fresco是關閉日志輸出的,你可以配置image pipeline讓它啟動.
Set<RequestListener> requestListeners = new HashSet<>();
requestListeners.add(new RequestLoggingListener());
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
// other setters
.setRequestListeners(requestListeners)
.build();
Fresco.initialize(context, config);
FLog.setMinimumLoggingLevel(FLog.VERBOSE);
- 檢視日志
你可以通過下面這條shell指令來檢視Fresco日志:
adb logcat -v threadtime | grep -iE 'LoggingListener|AbstractDraweeController|BufferedDiskCache'
它的輸出為如下格式:
- :: V unknown:RequestLoggingListener: time : onProducerStart: {requestId: , producer: EncodedMemoryCacheProducer}
- :: V unknown:RequestLoggingListener: time : onProducerFinishWithSuccess: {requestId: , producer: EncodedMemoryCacheProducer, elapsedTime: ms, extraMap: {cached_value_found=false}}
- :: V unknown:RequestLoggingListener: time : onProducerStart: {requestId: , producer: DiskCacheProducer}
- :: V unknown:BufferedDiskCache: Did not find image for http://www.example.com/image.jpg in staging area
- :: V unknown:BufferedDiskCache: Disk cache read for http://www.example.com/image.jpg
- :: V unknown:BufferedDiskCache: Disk cache miss for http://www.example.com/image.jpg
- :: V unknown:RequestLoggingListener: time : onProducerFinishWithSuccess: {requestId: , producer: DiskCacheProducer, elapsedTime: ms, extraMap: {cached_value_found=false}}
- :: V unknown:RequestLoggingListener: time : onProducerStart: {requestId: , producer: NetworkFetchProducer}
- :: V unknown:RequestLoggingListener: time : onProducerFinishWithSuccess: {requestId: , producer: NetworkFetchProducer, elapsedTime: ms, extraMap: null}
- :: V unknown:BufferedDiskCache: About to write to disk-cache for key http://www.example.com/image.jpg
- :: V unknown:RequestLoggingListener: time : onProducerStart: {requestId: , producer: DecodeProducer}
- :: V unknown:BufferedDiskCache: Successful disk-cache write for key http://www.example.com/image.jpg
- :: V unknown:RequestLoggingListener: time : onProducerFinishWithSuccess: {requestId: , producer: DecodeProducer, elapsedTime: ms, extraMap: {hasGoodQuality=true, queueTime=, bitmapSize=x400, isFinal=true}}
- :: V unknown:RequestLoggingListener: time : onRequestSuccess: {requestId: , elapsedTime: ms}
- :: V unknown:AbstractDraweeController: controller ebe0eb : set_final_result @ onNewResult: image: CloseableReference fd41bb0
在這個示例中,我們發現名為ebe0eb的 DraweeView 向名為的 DataSource 進行了圖像請求。
首先,圖檔沒有在記憶體緩存中找到,也沒有在磁盤緩存中找到,最後去網絡上下載下傳圖檔。下載下傳成功後,圖檔被解碼,之後請求結束。
最後資料源通知 controller 圖檔就緒,顯示圖檔(set_final_result)。
8.2:一些陷阱
- 不要使用 ScrollViews界
如果你想要在一個長的圖檔清單中滑動,你應該使用 RecyclerView,ListView,或 GridView。這三者都會在你滑動時不斷重用子視圖。Fresco 的 view 會接收系統事件,使它們能正确管理記憶體。
ScrollView 不會這樣做。是以,Fresco view 不會被告知它們是否在螢幕上顯示,并保持圖檔記憶體占用直到你的 Fragment 或 Activity 停止。
你的 App 将會面臨更大的 OOM 風險。
- 不要向下轉換
不要試圖把Fresco傳回的一些對象進行向下轉化,這也許會帶來一些對象操作上的便利
,但是也許在後續的版本中,你會遇到一些因為向下轉換特性丢失導緻的難以處理的問題。
- 不要使用getTopLevelDrawable
DraweeHierarchy.getTopLevelDrawable() 僅僅 應該在DraweeViews中用,除了定義View中,其他應用代碼建議連碰都不要碰這個。
在自定義view中,也千萬不要将傳回值向下轉換,也許下個版本,我們會更改這個傳回值類型。
- 不要複用 DraweeHierarchies
永遠不要把 DraweeHierarchy 通過 DraweeView.setHierarchy 設定給不同的View。
DraweeHierarchy 是由一系列 Drawable 組成的。在 Android 中, Drawable 不能被多個 View 共享。
- 不要在多個DraweeHierarchy中使用同一個Drawable
原因同上。不過你可以在占位圖、重試圖、錯誤圖中使用相同的資源ID,Android 實際會建立不同的 Drawable。
如果你使用GenericDraweeHierarchyBuilder,那麼需要調用Resources.getDrawable來通過資源擷取圖檔。
不過請不要隻調用一次然後将結果傳給不同的Hierarchy!
- 不要直接控制 hierarchy
不要直接使用 SettableDraweeHierarchy 方法(reset,setImage,…)。
它們應該僅由 controller 使用。
不要使用setControllerOverlay來設定一個覆寫圖,這個方法隻能給 controller 調用。
如果你需要顯示覆寫圖,可以參考Drawee的各種效果配置
- 不要直接給 DraweeView 設定圖檔
目前 DraweeView 直接繼承于 ImageView,是以它有 setImageBitmap,
setImageDrawable 等方法。
如果利用這些方法直接設定一張圖檔,内部的 DraweeHierarchy 就會丢失,也就無法取到image
pipeline 的任何圖像了。
- 使用 DraweeView 時,請不要使用任何 ImageView 的屬性
在後續的版本中,DraweeView 會直接從 View 派生。
任何屬于 ImageView 但是不屬于 View 的方法都會被移除。
8.3:為什麼不支援wrap_content
人們經常會問,為什麼Fresco中不可以使用wrap_content?
主要的原因是,Drawee永遠會在getIntrinsicHeight/getIntrinsicWidth中傳回-。
這麼做的原因是 Drawee 不像ImageView一樣。它同一時刻可能會顯示多個元素。比如在從占位圖漸變到目标圖
時,兩張圖會有同時顯示的時候。再比如可能有多張目标圖檔(低清晰度、高清晰度兩張)。如果這些圖像都是不同的尺寸,那麼很難定義”intrinsic”尺寸。
如果我們要先用占位圖的尺寸,等加載完成後再使用真實圖的尺寸,那麼圖檔很可能顯
示錯誤。它可能會被根據占位圖的尺寸來縮放、裁剪。
唯一防止這種事情的方式就隻有在圖檔加載完成後強制觸發一次layout。這樣的話不僅
會影響性能,而且會讓應用的界面突變,很影響使用者體驗!
如果使用者正在讀一篇文章,然後在圖檔加載完成後整篇文章突然向下移動,這是非常不好的。
是以你必須指定尺寸或者用match_parent來布局。
你如果從服務端請求圖檔,服務端可以做到傳回圖檔尺寸。然後你拿到之後通過setLayoutParams 來給View設定寬高。
當然如果你必須要使用wrap_content,那麼你可以參考StackOverflow上的一個回答。
但是我們以後會移除這個功能,Ugly things should look ugly。
8.4:共享元素動畫
使用 ChangeBounds,而不是ChangeImageTransform
Android (Lollipop) 引入了 共享元素動畫,允許在多個Activity切換時共享相同的View!
你可以在XML中定義這個變換。有個ChangeImageTransform變換可以在共享元素切換時對ImageView進行變換,可惜Fresco暫時不支援它,因為Drawee維護着自己的轉換Matrix。
幸運的是你可以有另一種做法:使用ChangeBounds。你可以改變layout的邊界,這樣Fresco會根據它進行自适應,也能夠達到你想要的功能。
9:總結
Fresco的使用在一定程度上要比Picasso、GLide要複雜,但是在強大的Fresco面前,複雜并不是理由,隻要靈活的使用Fresco,一定會給我們的開發帶來便利。
有關Fresco的任何問題可以檢視Fresco的中文API網址來尋求解決方案。
https://www.fresco-cn.org/docs/index.html
歡迎通路201216323.tech來檢視我的CSDN部落格。
歡迎關注我的個人技術公衆号,快速檢視我的最新文章。