1. 程式人生 > >android之Fresco框架(一)--Fresco基本使用

android之Fresco框架(一)--Fresco基本使用

當下最常用的圖片載入框架是:Gilde,Fresco,Picasso。Fresco是Facebook提供的開源圖片載入庫,它能夠從網路,本地儲存和Android資原始檔中載入圖片,且具有三級快取設計(2級記憶體,1級檔案)。Fresco中實現了各種載入過程以及載入後的圖片繪製,整體都很強大。所以準備來好好學學這個框架啦。

Frescp框架的設計主要採用的是MVC模式。DraweeView實現了View的功能,DraweeHierarchy實現了Model的功能,DraweeController實現Controller的功能。

基本用法

首先在build.gradle中新增依賴,目前已經更新到1.8.1版本了:

dependencies {
  // 在 API < 14 上的機器支援 WebP 時,需要新增
  //compile 'com.facebook.fresco:animated-base-support:1.8.1'

  // 支援 GIF 動圖,需要新增
  //compile 'com.facebook.fresco:animated-gif:1.8.1'

  // 支援 WebP (靜態圖+動圖),需要新增
  //compile 'com.facebook.fresco:animated-webp:1.8.1'
  //compile 'com.facebook.fresco:webpsupport:1.8.1'

  // 僅支援 WebP 靜態圖,需要新增
  compile 'com.facebook.fresco:webpsupport:1.8.1'
}

在Android studio會自動下載相應的依賴包。

在進行圖片載入之前,需要配置Fresco類,Fresco.initialize只需要呼叫一次,所以我們在Application中進行初始化:

public class MyApplication extends Application {
    @Override
    public void onCreate(){
        super.onCreate();
        Fresco.initialize(this);
    }
}

在AndroidManifest.xml中配置MyApplication,如果要從網路下載圖片,還需要新增網路訪問許可權:

<uses-permission android:name="android.permission.INTERNET" />
<application
    android:name=".MyApplication"
    ...
</application>

在xml中配置SimpleDraweeView

<com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/id_main_sdv"
    android:layout_width="20dp"        
    android:layout_height="20dp"
    fresco:placeholderImage="@drawable/imgbg"
/>

在Activity中載入圖片:

SimpleDraweeView sdv = (SimpleDraweeView) findViewById(R.id.id_main_sdv);
Uri uri = Uri.parse("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1523079111408&di=7783555b20885592a8034c6e729a6414&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01ea90595f5ca4a8012193a3d93648.jpeg");
sdv.setImageURI(uri);

接下來就是等待Fresco下載完圖片並展示出來了。

XML中配置使用Drawees

一個大致的XML配置例示如下:

    <com.facebook.drawee.view.SimpleDraweeView
        android:id="@+id/id_main_sdv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        fresco:actualImageScaleType="focusCrop"             //載入得到的圖片的縮放型別
        fresco:fadeDuration="1000"                          //進度條,佔位圖片消失,載入圖片展現的時間間隔
        fresco:failureImage="@drawable/imgbg"               //載入失敗之後顯示的圖片
        fresco:failureImageScaleType="centerInside"         //圖片縮放型別
        fresco:placeholderImage="@drawable/imgbg"           //佔位圖片(未載入之前顯示的圖片)
        fresco:placeholderImageScaleType="fitCenter"      
        fresco:progressBarAutoRotateInterval="1000"         //載入進度條圖片旋轉週期
        fresco:progressBarImage="@drawable/progress_bar"    //載入進度條圖片
        fresco:progressBarImageScaleType="centerInside"
        fresco:retryImage="@mipmap/ic_launcher"             //提示重新載入的圖片資源
        fresco:retryImageScaleType="centerCrop"
        fresco:backgroundImage="@color/colorWhite"          //背景圖片
        fresco:roundAsCircle="false"                        //是否要將圖片剪下成圓形
        fresco:viewAspectRatio="1"                          //圖片寬高比
        fresco:overlayImage="@drawable/overlay"             //在圖片上方覆蓋一個圖片資源
        fresco:pressedStateOverlayImage="@color/colorBlack"
        fresco:roundedCornerRadius="20dp"                   //圓角角度,
        fresco:roundTopLeft="true"                          //設定哪個角需要變成圓角
        fresco:roundTopRight="false"
        fresco:roundBottomLeft="false"
        fresco:roundBottomRight="true"
        fresco:roundWithOverlayColor="@color/colorWhite"    //圓角部分填充色
        fresco:roundingBorderWidth="2dp"                    //邊框寬度
        fresco:roundingBorderColor="@color/colorBlack"      //邊框填充色
        />

注意的是,在設定寬和高的時候,一定要設定成固定值,因為下載的圖片,原始的佔位圖片,出錯後的圖片,這些的尺寸可能各不相同,如果用wrap_content,View將重新layout。

在一種情況下可以用wrap_content,就是我們設定了viewAspectRatio屬性確定寬高比,這樣我們將其中一個屬性設為定值,另一個屬性設為warp_content,可以通過計算寬高比得到目標值。

DraweeHierarchy和DraweeHierarchyBuilder

在Java中配置需要用到DraweeHierarchy,如前面所說,DraweeHierarchy類似於MVC中的Model,所以我們對最後顯示的drawable的顯示設定都在著裡面進行。

下面的程式碼實現了跟上面XML基本一致的功能,至於dp轉float我就沒去弄了。

//新建一個DraweeHierarchyBuilder類
GenericDraweeHierarchyBuilder builder =new GenericDraweeHierarchyBuilder(getResources());
//設定的到圖片的縮放型別
builder.setActualImageScaleType(ScalingUtils.ScaleType.FOCUS_CROP);
//縮放型別為focusCrop時,需要設定一個居中點
//(0f,0f)表示左上對齊顯示,(1f,1f)表示右下對齊顯示
PointF pf = new PointF(1f,1f);
builder.setActualImageFocusPoint(pf);
//進度條,佔位圖片消失,載入圖片展現的時間間隔
builder.setFadeDuration(1000);
//載入失敗之後顯示的圖片及圖片縮放型別
builder.setFailureImage(R.drawable.imgbg, ScalingUtils.ScaleType.CENTER_INSIDE);
//設定佔位圖片及縮放型別
builder.setPlaceholderImage(R.drawable.imgbg, ScalingUtils.ScaleType.FIT_CENTER);
//載入進度條圖片及縮放型別
builder.setProgressBarImage(R.drawable.progress_bar, ScalingUtils.ScaleType.CENTER_INSIDE);
//提示重新載入的圖片及縮放型別
builder.setRetryImage(R.mipmap.ic_launcher, ScalingUtils.ScaleType.CENTER_CROP);
//設定背景圖片
builder.setBackground(getResources().getDrawable(R.color.colorWhite));
//在圖片上方覆蓋一個圖片資源
builder.setOverlay(getResources().getDrawable(R.drawable.overlay));
builder.setPressedStateOverlay(getResources().getDrawable(R.color.colorBlack));
RoundingParams rp = new RoundingParams();
//是否要將圖片剪下成圓形
rp.setRoundAsCircle(false);
//設定哪個角需要變成圓角
rp.setCornersRadii(100f,0f,100f,0f);
//圓角部分填充色
rp.setOverlayColor(getResources().getColor(R.color.colorWhite));
//邊框寬度
rp.setBorderWidth(20f);
//邊框填充色
rp.setBorderColor(getResources().getColor(R.color.colorBlack));
builder.setRoundingParams(rp);
//得到DraweeHierarchy例項
GenericDraweeHierarchy hierachy = builder.build();
//在Drawee中設定DraweeHierarchy
sdv.setHierarchy(hierachy);

除了這些之外,在Java中還可以設定在圖片上方覆蓋多個圖片資源,我們也可以在載入的時候使用Fresco自定義的載入條。

//多個圖片資源
List<Drawable> overlaysList;
builder.setOverlays(overlaysList);
//進度條
builder.setProgressBarImage(new ProgressBarDrawable());

上面基本介紹了MVC中的Model和View,接下來就來介紹一下Controller了。

DraweeController和DraweeControllerBuilder

DraweeController主要是用於對圖片進行更多的控制和定製,我們可以設定對載入事件進行監聽,對載入之後的圖片進行壓縮或者修改,可以在載入失敗之後重新載入圖片,也可以實現多圖請求。

在程式碼中,我們利用DraweeControllerBuilder生成一個DraweeController例項,再將其傳給SimpleDraweeView就可以了。

PipelineDraweeControllerBuilder sdcb = Fresco.newDraweeControllerBuilder();
//設定uri
sdcb.setUri(uri);
//載入失敗之後,點選提示重新載入的圖片資源重新載入
sdcb.setTapToRetryEnabled(true);
//在指定一個新的controller的時候,使用setOldController,這可節省不必要的記憶體分配。
sdcb.setOldController(sdv.getController());
DraweeController controller = sdcb.build();
sdv.setController(controller);

在Controller中,如果我們想要對下載事件進行監聽,需要為Controller設定一個自定義的ControllerListener。

ControllerListener

在ControllerListener中可以對我們的下載過程進行監聽。程式碼如下

ControllerListener listener = new BaseControllerListener<ImageInfo>(){
    @Override
    public void onSubmit(String id, Object callerContext) {
        //提交請求之前呼叫的方法
        Log.d(TAG, "onSubmit: " + id);
    }
    @Override
    public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
        // 所有圖片都載入成功時觸發的方法
        Log.d(TAG, "onFinalImageSet: " + id);
    }

    @Override
    public void onIntermediateImageSet(String id, ImageInfo imageInfo) {
        //當中間圖片下載成功的時候觸發,用於多圖請求
    }

    @Override
    public void onIntermediateImageFailed(String id, Throwable throwable) {
        //當中間圖片下載失敗的時候觸發,用於多圖請求
    }

    @Override
    public void onFailure(String id, Throwable throwable) {
        // 載入圖片失敗時回撥的方法
        Log.d(TAG, "onFailure: " + id);
    }
    @Override
    public void onRelease(String id) {
        //釋放圖片資源時載入的方法
        Log.d(TAG, "onRelease: " + id);
    }
};
PipelineDraweeControllerBuilder sdcb = Fresco.newDraweeControllerBuilder();
sdcb.setControllerListener(listener);

如果我們在請求圖片的過程中,需要進行更多的操作的話,我們需要用ImageRequest來進行實現。

ImageRequest

ImageRequest支援很多的功能,例如自動旋轉圖片,漸進式載入,圖片縮放,後處理器,圖片複用,最低請求級別等等。基本的實現原始碼如下:

ImageRequest request = ImageRequestBuilder
                        //設定URI
                        .newBuilderWithSource(uri)
                        //自動旋轉
                        .setAutoRotateEnabled(true)
                        //最低級別請求
                        .setLowestPermittedRequestLevel(ImageRequest.RequestLevel.FULL_FETCH)
                        //圖片縮放
                        .setResizeOptions(new ResizeOptions(width,height))
                        //漸進式載入
                        .setProgressiveRenderingEnabled(false)
                        .build();
PipelineDraweeControllerBuilder sdcb = Fresco.newDraweeControllerBuilder();
sdcb.setImageRequest(request);
DraweeController controller = sdcb.build();
sdv.setController(controller);

最低請求級別包含以下幾種取值:

BITMAP_MEMORY_CACHE檢查記憶體快取,有如,立刻返回。這個操作是實時的。
ENCODED_MEMORY_CACHE檢查未解碼的圖片快取,如有,解碼並返回。
DISK_CACHE檢查磁碟快取,如果有載入,解碼,返回。
FULL_FETCH下載或者載入本地檔案。調整大小和旋轉(如有),解碼並返回。
圖片複用

假設一張圖片具有多個URI,例如某張圖片有各種比例的壓縮圖。我們在載入的時候,按順序查詢這些URI對應的圖片是否存在,一旦找到第一個存在的,就進行展示。這也能有效避免我們去載入過大的圖片。實現程式碼如下:

ImageRequest request1 = ImageRequest.fromUri(uri1);
ImageRequest request2 = ImageRequest.fromUri(uri);
ImageRequest[] request = {request1,request2};
PipelineDraweeControllerBuilder sdcb = Fresco.newDraweeControllerBuilder();
sdcb.setFirstAvailableImageRequests(request);
DraweeController controller = sdcb.build();
sdv.setController(controller);

後處理器:PostProcessor

PostProcessor主要用於對載入完的圖片進行處理,圖片在進入後處理器(postprocessor)的圖片是原圖的一個完整拷貝,原來的圖片不受修改的影響。在5.0以前的機器上,拷貝後的圖片也在native記憶體中。

在開始一個圖片顯示時,即使是反覆顯示同一個圖片,在每次進行顯示時,都需要指定後處理器。對於同一個圖片,每次顯示可以使用不同的後處理器。

通常通過繼承BaseProcessor來實現處理,通過重寫Process方法來進行圖片處理。BaseProcessor中提供了三個不同輸入形參的process方法來給我們實現重寫。

public class MyPostprocessor extends BasePostprocessor {
    
    @Override
    public String getName() {
        return "redMeshPostprocessor";
    }

    //對圖片進行即時後處理
    //程式碼中在圖片加上紅點
    @Override
    public void process(Bitmap bitmap) {
        for (int x = 0; x < bitmap.getWidth(); x+=4) {
            for (int y = 0; y < bitmap.getHeight(); y+=4) {
                bitmap.setPixel(x, y, Color.RED);
            }
        }
    }
    
    //對圖片無法進行即時處理
    //程式碼中對圖片進行反轉,目標圖片的處理需要用到原始圖片的資訊 
    @Override
    public void process(Bitmap destBitmap, Bitmap sourceBitmap) {
        super.process(destBitmap, sourceBitmap);

        for (int x = 0; x < destBitmap.getWidth(); x++) {
            for (int y = 0; y < destBitmap.getHeight(); y++) {
                destBitmap.setPixel(destBitmap.getWidth() - 1 - x, y, sourceBitmap.getPixel(x, y));
            }
        }
    }

    //只用於處理後圖片和源圖片大小不一樣的情況
    //程式碼中將圖片長寬都壓縮為原來的1/4
    @Override
    public CloseableReference<Bitmap> process(
            Bitmap sourceBitmap,
            PlatformBitmapFactory bitmapFactory) {
        int scale = 4;
        CloseableReference<Bitmap> bitmapRef = bitmapFactory.createBitmap(
                sourceBitmap.getWidth() / scale,
                sourceBitmap.getHeight() / scale);
        try {
            Bitmap destBitmap = bitmapRef.get();
            for (int x = 0; x < destBitmap.getWidth(); x++) {
                for (int y = 0; y < destBitmap.getHeight(); y++) {
                    destBitmap.setPixel(x, y, sourceBitmap.getPixel(scale*x, scale*y));
                }
            }
            return CloseableReference.cloneOrNull(bitmapRef);
        } finally {
            CloseableReference.closeSafely(bitmapRef);
        }
    }
    
}

說到這裡,對於process方法,看起來像是直接對圖片進行處理,那是怎麼保證對原圖的拷貝呢。這裡看一下BaseProcessor的程式碼。

public void process(Bitmap destBitmap, Bitmap sourceBitmap) {
    internalCopyBitmap(destBitmap, sourceBitmap);
    process(destBitmap);
}
private static void internalCopyBitmap(Bitmap destBitmap, Bitmap sourceBitmap) {
    if (destBitmap.getConfig() == sourceBitmap.getConfig()) {
      Bitmaps.copyBitmap(destBitmap, sourceBitmap);
    } else {
      // The bitmap configurations might be different when the source bitmap's configuration is
      // null, because it uses an internal configuration and the destination bitmap's configuration
      // is the FALLBACK_BITMAP_CONFIGURATION. This is the case for static images for animated GIFs.
      Canvas canvas = new Canvas(destBitmap);
      canvas.drawBitmap(sourceBitmap, 0, 0, null);
    }
}

從上面可以看出,在呼叫process之前,會先對原圖進行復制,之後再進行process(destBitmap)操作。同樣,看一下BaseProcessor中process(sourceBitmap,bitmapFactory)的定義:

@Override
public CloseableReference<Bitmap> process(
    Bitmap sourceBitmap,
    PlatformBitmapFactory bitmapFactory) {
    final Bitmap.Config sourceBitmapConfig = sourceBitmap.getConfig();
    CloseableReference<Bitmap> destBitmapRef =
        bitmapFactory.createBitmapInternal(
            sourceBitmap.getWidth(),
            sourceBitmap.getHeight(),
            sourceBitmapConfig != null ? sourceBitmapConfig : FALLBACK_BITMAP_CONFIGURATION);
    try {
        process(destBitmapRef.get(), sourceBitmap);
        return CloseableReference.cloneOrNull(destBitmapRef);
    } finally {
        CloseableReference.closeSafely(destBitmapRef);
    }
}

可以看出,在BaseProcessor中幾個process的呼叫順序分別是:

BaseProcessor中process(sourceBitmap,bitmapFactory)->process(destBitmap,sourceBitmap)->process(destBitmap)

在教程上說不能重寫多個process方法,其實實際上是可以的,而且利用這個還能疊加自定義操作,例如反轉之後加紅點什麼的。

在DraweeController中新增PostProcessor的程式碼如下:

//後處理器
Postprocessor processor = new MyPostprocessor();
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri).setPostprocessor(processor).build();
PipelineDraweeControllerBuilder sdcb = Fresco.newDraweeControllerBuilder();
sdcb.setImageRequest(request);
DraweeController controller = sdcb.build();
sdv.setController(controller);
關於Fresco的基本使用大概就是這樣了。漸進式JPEG圖,動畫支援,多圖請求這些因為暫時沒有用到(沒有對應圖片),就等到以後用到的時候再來補充了。