天天看點

Fresco源碼解析 - DraweeView

Fresco源碼解析 - DraweeView

DraweeView 是 Fresco 的“門面”,負責顯示由 DraweeHierarchy 提供的資料(Placeholder、Actual Image、Progress Drawable etc),DraweeController 作為幕後,負責擷取資料,關于三者的關系,上一篇博文 - Fresco源碼解析 - Hierarchy / View / Controller 已經做了初步介紹,從本篇開始會逐個分析每一部分的源碼,各個擊破。

繼承體系

DraweeView 并不是一個簡單的自定義 View,它必須要提供與 DraweeHierarchy 和 DraweeController 互動的接口,DraweeView 的繼承關系如下圖所示:

Fresco源碼解析 - 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

的具體用法,猛戳這裡。

繼續閱讀