DraweeView 是 Fresco 的“門面”,負責顯示由 DraweeHierarchy 提供的資料(Placeholder、Actual Image、Progress Drawable etc),DraweeController 作為幕後,負責擷取資料,關于三者的關系,上一篇博文 - Fresco源碼解析 - Hierarchy / View / Controller 已經做了初步介紹,從本篇開始會逐個分析每一部分的源碼,各個擊破。
繼承體系
DraweeView 并不是一個簡單的自定義 View,它必須要提供與 DraweeHierarchy 和 DraweeController 互動的接口,DraweeView 的繼承關系如下圖所示:
用法
一般比較常見的用法是在xml布局檔案中直接定義一個
SimpleDraweeView
,這也是最簡單的一種用法,因為不需要自己為
SimpleDraweeView
提供 Hierarchy 和 Controller。
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="130dp"
android:layout_height="130dp"
fresco:placeholderImage="@drawable/my_drawable" />
GenericDraweeView 實作了解析xml屬性的功能,它提供了兩種構造方式,一種是直接使用外部 hierarchy,另一種會根據屬性值在内部建構。
/** 使用外部hierarchy */
public GenericDraweeView(Context context, GenericDraweeHierarchy hierarchy) {
super(context);
setHierarchy(hierarchy);
}
public GenericDraweeView(Context context) {
super(context);
inflateHierarchy(context, null);
}
public GenericDraweeView(Context context, AttributeSet attrs) {
super(context, attrs);
inflateHierarchy(context, attrs);
}
public GenericDraweeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
inflateHierarchy(context, attrs);
}
xml屬性
inflateHierarchy
會根據布局檔案中使用的屬性生成一個Hierarchy,如果沒有設定屬性并且也沒有外部的 Hierarchy,
GenericDraweeView
會使用預設值來建立
Hierarchy
,預設值由
GenericDraweeHierarchyBuilder
提供。
/**
* Class to construct a GenericDraweeHierarchy.
*
*/
public class GenericDraweeHierarchyBuilder {
public static final int DEFAULT_FADE_DURATION = 300;
public static final ScaleType DEFAULT_SCALE_TYPE = ScaleType.CENTER_INSIDE;
public static final ScaleType DEFAULT_ACTUAL_IMAGE_SCALE_TYPE = ScaleType.CENTER_CROP;
// ...
}
inflateHierarchy
方法使用
GenericDraweeHierarchyBuilder
生成
GenericDraweeHierarchy
。
GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(resources);
// set fade duration
builder.setFadeDuration(fadeDuration);
// set images & scale types
if (placeholderId > 0) {
builder.setPlaceholderImage(resources.getDrawable(placeholderId), placeholderScaleType);
}
// 省略其他set
setHierarchy(builder.build());
設定長寬比(Aspect Ratio)
長寬比暫時不支援在xml中設定,隻能通過調用
setAspectRatio
來設定。
setAspectRatio
方法會調用
requestLayout()
,然後觸發
onMeasure
,根據
aspect ratio
重新計算 View 的高度和寬度。
/**
* Sets the desired aspect ratio (w/h).
*/
public void setAspectRatio(float aspectRatio) {
if (aspectRatio == mAspectRatio) {
return;
}
mAspectRatio = aspectRatio;
requestLayout();
}
/**
* Gets the desired aspect ratio (w/h).
*/
public float getAspectRatio() {
return mAspectRatio;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mMeasureSpec.width = widthMeasureSpec;
mMeasureSpec.height = heightMeasureSpec;
AspectRatioMeasure.updateMeasureSpec(
mMeasureSpec,
mAspectRatio,
getLayoutParams(),
getPaddingLeft() + getPaddingRight(),
getPaddingTop() + getPaddingBottom());
super.onMeasure(mMeasureSpec.width, mMeasureSpec.height);
}
onMeasure
方法中用到的
updateMeasureSpec
是
com.facebook.drawee.view.AspectRatioMeasure
的一個靜态方法,實作了重制計算高度和寬度的功能。
public static void updateMeasureSpec(
Spec spec,
float aspectRatio,
ViewGroup.LayoutParams layoutParams,
int widthPadding,
int heightPadding) {
if (aspectRatio <= 0) {
return;
}
if (shouldAdjust(layoutParams.height)) {
int widthSpecSize = View.MeasureSpec.getSize(spec.width);
int desiredHeight = (int) ((widthSpecSize - widthPadding) / aspectRatio + heightPadding);
int resolvedHeight = View.resolveSize(desiredHeight, spec.height);
spec.height = View.MeasureSpec.makeMeasureSpec(resolvedHeight, View.MeasureSpec.EXACTLY);
} else if (shouldAdjust(layoutParams.width)) {
int heightSpecSize = View.MeasureSpec.getSize(spec.height);
int desiredWidth = (int) ((heightSpecSize - heightPadding) * aspectRatio + widthPadding);
int resolvedWidth = View.resolveSize(desiredWidth, spec.width);
spec.width = View.MeasureSpec.makeMeasureSpec(resolvedWidth, View.MeasureSpec.EXACTLY);
}
}
private static boolean shouldAdjust(int layoutDimension) {
// Note: wrap_content is supported for backwards compatibility, but should not be used.
return layoutDimension == 0 || layoutDimension == ViewGroup.LayoutParams.WRAP_CONTENT;
}
使用
setAspectRatio
時,如果
layout_width
或
layout_height
設定成了
0dp
, 那麼
MeasureSpec
的
Mode
不能是
EXACTLY
,否則
View.MeasureSpec.getSize(spec.height)
的傳回值會變成在布局檔案中設定的值,而不是根據長寬比計算出來的值。
如果使用了
android:layout_width="200dp"
, 隻有加上
android:layout_weight
了才可以正常使用 aspect ratio。
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/circle_img"
android:layout_width="200dp"
android:layout_height="0dp"
android:layout_weight="1"
fresco:actualImageScaleType="centerCrop" />
當然直接使用
wrap_content
也可以。
關于
SimpleDraweeView
的具體用法,猛戳這裡。