上篇文章我們分析了Fresco中的DraweeView,對其中的一些原理以及方法進行了解析。在這過程中我們瞭解到,DraweeView中是通過DraweeHolder來統一管理的。而DraweeHolder又是用來統一管理相關的Hierarchy與Controller,如果想了解DraweeView相關的知識,可以先看下我的前一篇文章Fresco原始碼分析之DraweeView。今天這裡進一步來分析Fresco中的Hierarchy。
GenericDraweeHierarchyBuilder
在GenericDraweeView的構造方法中會呼叫*inflateHierarchy(context, atts)*方法來建立一個GenericDraweeHierarchyBuilder物件,通過呼叫該物件的build方法來生成一個Hierarchy。
如果你看了我上一篇文章,相信對這個方法你會感到很親切。
所以這個類的主要作用是用來建立一個Hierarchy,通過builder模式來例項化包含相關資訊的Hierarchy。如果你一步步深入瞭解Fresco的話,相信你對builder模式也將習以為常,因為後面你將經常與它碰面。這也是Fresco開源專案的主要設計模式。在該builder類中主要構建的資訊有:
- Drawable相關:placeholderImage、retryImage、failureImage、progressBarImage、background、overlays與pressedStateOverlay
- ScaleType相關:與Drawable相對應的placeholderImageScaleType、retryImageScaleType等等。
- 其它:fadeDuration漸變過渡時間與RoundingParams圓角相關資訊等等
GenericDraweeHierarchy
通過上面的GenericDraweeHierarchyBuilder的build方法會建立一個GenericDraweeHierarchy物件。這就是我們今天需要主要分析的類,也是Fresco的一個核心類。
SettableDraweeHierarchy
首先我們來分析一下它的類結構,它會實現SettableDraweeHierarchy介面,我們進入該介面發現一共有6個介面方法,它們分別為:
- void reset(); 重新初始化Hierarchy
- void setImage(Drawable drawable, float progress, boolean immediate); 設定實際需要展示的圖片,其中progress表示圖片的載入質量進度(在漸進式中會使用到)
- void setProgress(float progress, boolean immediate); 更新圖片載入進度
- void setFailure(Throwable throwable); 圖片載入失敗時呼叫,可以設定failureImage
- void setRetry(Throwable throwable); 當圖片載入失敗時重新進行載入,可以設定retryImage
- void setControllerOverlay(Drawable drawable); 用來設定圖層覆蓋
這些方法在GenericDraweeHierarchy中都會做出相應的實現,同時最終都會在對應的DraweeView中的Controller來呼叫。
至於這些方法中的實現細節,由於程式碼比較多,這裡就不一一列舉出來,大家可以自行檢視GenericDraweeHierarchy的原始碼。
DraweeHierarchy
上面的SettableDraweeHierarchy還有一個父類介面為DraweeHierarchy,在這個介面中只有一個介面方法為Drawable getTopLevelDrawable();。是不是對這個方法也有點熟悉呢?(路人甲:嗯,好像上篇文章提及過!)它是用來獲取檢視樹的最頂層檢視,其實說白了就是顯示出來的Drawable。它主要在DraweeHolder中呼叫,最終也會由void setHierarchy(DH hierarchy) 與 void setController(@Nullable DraweeController draweeController) 來呼叫
super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());
複製程式碼
從而顯示需要展示的圖片。
下面我們回到之前的GenericDraweeHierarchy類。在開始分析它之前,我們先來了解一下它的另一個感念-層級樹或者說圖層樹
,我這裡就把它說成圖層樹吧。那麼我們來看下Fresco的圖層樹是什麼樣的:
* o RootDrawable (top level drawable)
* |
* +--o FadeDrawable
* |
* +--o ScaleTypeDrawable (placeholder branch, optional)
* | |
* | +--o Drawable (placeholder image)
* |
* +--o ScaleTypeDrawable (actual image branch)
* | |
* | +--o ForwardingDrawable (actual image wrapper)
* | |
* | +--o Drawable (actual image)
* |
* +--o null (progress bar branch, optional)
* |
* +--o Drawable (retry image branch, optional)
* |
* +--o ScaleTypeDrawable (failure image branch, optional)
* |
* +--o Drawable (failure image
複製程式碼
根據上面所展示的層次結構,我們可以發現最底層是由RootDrawable來構成,它有一個直接子分支為FadeDrawable。而在FadeDrawable中又有5個直接子分支,分別為placeholder branch、actual image branch、progressBar image branch、retry image branch與failure image branch。至於這些image的作用相信不用我再多做說明了,這些image除了actual image是必須要明確指定的,其它的都是可選擇的配置。因此RootDrawable與FadeDrawable是一定存在的。雖然其它的都是可選的配置,但無論你是否選擇了,它們的層級結構都會保留在圖層樹中。還有每一個層級都有自己獨立的scale type,當然rounding(圓角)也是支援的。
其實除了這5個分支,與它們同一層次的還有background image與overlay image,它們分別位於placeholder image之前與failure image之後。background相信都知道,因為圖片都支援background與src,overlay為圖層覆蓋。至於為什麼沒有在上面的結構中顯示,我這裡也不得而知,猜測可能是這兩個並不是Fresco主要常用的特性。
那麼這些圖層結構是通過layers陣列來體現的,可以來看下GenericDraweeHierarchy的原始碼
GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) {
mResources = builder.getResources();
mRoundingParams = builder.getRoundingParams();
mActualImageWrapper = new ForwardingDrawable(mEmptyActualImageDrawable);
int numOverlays = (builder.getOverlays() != null) ? builder.getOverlays().size() : 1;
numOverlays += (builder.getPressedStateOverlay() != null) ? 1 : 0;
// layer indices and count
int numLayers = OVERLAY_IMAGES_INDEX + numOverlays;
// array of layers
Drawable[] layers = new Drawable[numLayers];
layers[BACKGROUND_IMAGE_INDEX] = buildBranch(builder.getBackground(), null);
layers[PLACEHOLDER_IMAGE_INDEX] = buildBranch(
builder.getPlaceholderImage(),
builder.getPlaceholderImageScaleType());
layers[ACTUAL_IMAGE_INDEX] = buildActualImageBranch(
mActualImageWrapper,
builder.getActualImageScaleType(),
builder.getActualImageFocusPoint(),
builder.getActualImageColorFilter());
layers[PROGRESS_BAR_IMAGE_INDEX] = buildBranch(
builder.getProgressBarImage(),
builder.getProgressBarImageScaleType());
layers[RETRY_IMAGE_INDEX] = buildBranch(
builder.getRetryImage(),
builder.getRetryImageScaleType());
layers[FAILURE_IMAGE_INDEX] = buildBranch(
builder.getFailureImage(),
builder.getFailureImageScaleType());
if (numOverlays > 0) {
int index = 0;
if (builder.getOverlays() != null) {
for (Drawable overlay : builder.getOverlays()) {
layers[OVERLAY_IMAGES_INDEX + index++] = buildBranch(overlay, null);
}
} else {
index = 1; // reserve space for one overlay
}
if (builder.getPressedStateOverlay() != null) {
layers[OVERLAY_IMAGES_INDEX + index] = buildBranch(builder.getPressedStateOverlay(), null);
}
}
// fade drawable composed of layers
mFadeDrawable = new FadeDrawable(layers);
mFadeDrawable.setTransitionDuration(builder.getFadeDuration());
// rounded corners drawable (optional)
Drawable maybeRoundedDrawable =
WrappingUtils.maybeWrapWithRoundedOverlayColor(mFadeDrawable, mRoundingParams);
// top-level drawable
mTopLevelDrawable = new RootDrawable(maybeRoundedDrawable);
mTopLevelDrawable.mutate();
resetFade();
}
複製程式碼
根據原始碼可以明顯的看出不管overlay是否存在,它都會保留圖層層次,當然如果存在的話,就根據實際情況在OVERLAY_IMAGES_INDEX之後增加圖層層次。layers是一個Drawable陣列,這裡通過Drawable buildBranch(@Nullable Drawable drawable, @Nullable ScaleType scaleType) 方法來建立對應的Drawable。在最後,建立了FadeDrawable並將layers傳遞給它,在這裡FadeDrawable的特性應該有點眉目了吧,其實它內部做的就是對Drawable陣列進行操作。之後RootDrawable也出現了(關於Drawable文章後面會統一分析),這樣之前所提到的層級結構就形成了。
既然actual image是一定存在的,那麼在它真正展示之前image中的顯示用什麼來控制的呢?其實就是我們之前所提到的SettableDraweeHierarchy來控制。在真實的圖片展示出來之前,它可以用來展示placeholder image等相關圖層。具體的我們可以來看一下它裡面實現的一些方法。
setImage
這裡就拿void setImage(Drawable drawable, float progress, boolean immediate) 來進行深入分析,那麼下面來看下它的原始碼:
@Override
public void setImage(Drawable drawable, float progress, boolean immediate) {
drawable = WrappingUtils.maybeApplyLeafRounding(drawable, mRoundingParams, mResources);
drawable.mutate();
mActualImageWrapper.setDrawable(drawable);
mFadeDrawable.beginBatchMode();
fadeOutBranches();
fadeInLayer(ACTUAL_IMAGE_INDEX);
setProgress(progress);
if (immediate) {
mFadeDrawable.finishTransitionImmediately();
}
mFadeDrawable.endBatchMode();
}
複製程式碼
首先該方法會使用WrappingUtils工具類並且結合RoundingParams引數來重新生成一個可以附帶圓角的Drawable。然後將新的Drawable交由mActualImageWrapper,結合上面的層次結果分析,會很容易知道這就是需要真實顯示的圖層。它是一個ForwardingDrawable型別,該類主要是對傳入的目標Drawable進行相應的原生方法操作。下一步呼叫FadeDrawable的*beginBatchMode()方法,該方法的作用主要為了圖層批處理做標記,防止在批處理時進行invalidate操作。直到最後的endBatchMode()方法呼叫之後才標識著圖層批處理操作結束。該批處理操作的目的是將actual image圖層顯示出來,所以首先呼叫fadeOutBranches()*方法:
private void fadeOutBranches() {
fadeOutLayer(PLACEHOLDER_IMAGE_INDEX);
fadeOutLayer(ACTUAL_IMAGE_INDEX);
fadeOutLayer(PROGRESS_BAR_IMAGE_INDEX);
fadeOutLayer(RETRY_IMAGE_INDEX);
fadeOutLayer(FAILURE_IMAGE_INDEX);
}
複製程式碼
這裡對上述提到的所有圖層進行*fadeOutLayer()操作,繼續進入fadeOutLayer()*方法
private void fadeOutLayer(int index) {
if (index >= 0) {
mFadeDrawable.fadeOutLayer(index);
}
}
複製程式碼
發現也很簡單,無非就是呼叫了FadeDrawable的*fadeOutLayer()*方法。
public void fadeOutLayer(int index) {
mTransitionState = TRANSITION_STARTING;
mIsLayerOn[index] = false;
invalidateSelf();
}
複製程式碼
在之前已經提到過FadeDrawable本質上可以理解為時一個Drawable陣列,內部都是圍繞著陣列整體進行操作。對應的還有fadeInLayer()
private void fadeInLayer(int index) {
if (index >= 0) {
mFadeDrawable.fadeInLayer(index);
}
}
複製程式碼
在FadeDrawable中除了用來儲存相應的Drawable陣列mLayers,還有與其相對應的mIsLayerOn布林陣列,該陣列用來標識各個Hierarchy中的圖層是否需要展示。所以*fadeOutLayer()是對index處的圖層進行隱藏標識。最終的顯隱操作都會轉化為在void draw(Canvas canvas)*方法中進行alpha操作。
那麼再回到之前的*setImage()*方法中,*fadeOutBranches()是對相關的圖層進行隱藏標識,然後再通過fadeInLayer(ACTUAL_IMAGE_INDEX)方法改變actual image圖層的標識,將它改變成顯示狀態。最後如果有progressBar image圖層的話,也將會由setProgress(progress)*方法來體現。immediate是用來判斷是否之後的顯隱操作立馬實現或者漸變過渡實現(內部就是對alpha進行百分比操作,內部有個mDurationMs,該時間值也是文章開頭提到的builder中的fadeDuration)。這樣整個的實際圖片展示流程我們已經分析完畢,所以Hierarchy中最重要的還是對圖層概念的理解。下面再對GenericDraweeHierarchy中的一些其它方法進行簡要的說明:
- Drawable buildActualImageBranch(Drawable drawable, @Nullable ScaleType scaleType, @Nullable PointF focusPoint, @Nullable ColorFilter colorFilter) 構建實際展示圖片的圖層分支,內部對於Drawable的建立還是藉助WrappingUtils工具類
- Drawable buildBranch(@Nullable Drawable drawable, @Nullable ScaleType scaleType) 構建除實際展示圖片之外的其它圖層分支,Drawable的建立也是藉助WrappingUtils工具類
- void resetFade() 初始化圖層結構
主要的就這幾個吧,其它的都已經在上面分析流程中詳細說明了。
Drawable
最後再整理一下Fresco中的一些相關的自定義的Drawable子類
- ArrayDrawable:Drawable陣列的集合體,通過layers陣列來管理Drawable,內部的都是對陣列集合中的每一個Drawable進行操作,類似與Android原生的LayerDrawable,只是它並不支援add與remove操作
- ForwardingDrawable:對傳入的目標Drawable即操作物件進行封裝處理,該新類的方法可以呼叫目標物件對應的方法,同時保留目標Drawable的各個狀態,不依賴與目標類的細節實現,提高新類的穩定性,該方式可以稱之為複合。Fresco中絕大多數自定義Drawable都是它的子類。
- AutoRotateDrawable:它繼承於ForwardingDrawable,實現的是對Drawable的旋轉操作
- FadeDrawable:它繼承於ArrayDrawable,之前也詳細提到過,Hierarchy中的主要圖層集合體。主要是通過mLayers與mIsLayerOn陣列來控制陣列中各個Drawable的alpha值,即顯隱
- MatrixDrawable:它繼承於ForwardingDrawable,顧名思義通過矩陣來改變Drawable狀態。
- OrientedDrawable:它也繼承於ForwardingDrawable,它不同於AutoRotateDrawable的是,它只支援90度的倍數角度旋轉。
- ProgressBarDrawable:進度條Drawable,支援橫豎方向。
- RoundedBitmapDrawable:繼承於BitmapDrawable,根據Bitmap來建立有關圓角的Drawable,主要在WrappingUtils類中使用,用例構建全新的圓角Drawable
- RoundedColorDrawable:繼承於Drawable,根據Color來建立圓角Drawable,主要在WrappingUtils類中使用,用例構建全新的圓角Drawable
- RoundedCornersDrawable:繼承於ForwardingDrawable,用於設計overLay覆蓋圖片圓角,主要在WrappingUtils類中使用
- ScaleTypeDrawable:繼承於ForwardingDrawable,用於縮放型別的Drawable
End
本篇文章主要是分析Fresco中有關Hierarchy相關的實現與原理,通過分析發現Hierarchy中都是對Drawable圖層進行處理,並沒有其它的快取、請求之類的邏輯。所以如果你使用Fresco的時候只使用Hierarchy的話,就與別的ImageView沒有多大的區別,真正的圖層操作與快取控制都在Controller中,所以下篇文章將進入Controller解析,來詳細瞭解它的實現細節。