手记

Fresco源码分析(2) - GenericDraweeHierarchy构建图层

1 引言

DraweeHierarchy是所有Hierarchy的父接口,它内部只提供了一个基本而又不可缺失的功能:获取图层树的父节点图层。不过仅仅只有这个功能是不够的,Fresco紧接着用接口SettableHierarchy来继承它,声明一些具体的功能:

o    void reset() 重置所有图层(慎用)

o    void setImage(Drawable drawable, float progress, boolean immediate) 设置目标显示图片,progress表示图片的加载质量(在渐进式图中使用),immediate设置为true时图片会马上显示(而不是有一个渐变的过程)。但是如果你使用这个函数来设置目标显示图片,将意味着你在加载这张图片的时候放弃了Fresco的Image Pipeline加载图片的方式。

o    void setProgress(float progress, boolean immediate) 设置ProgressBar的进度。当progress为1时会隐藏ProgressBar,同样immediate设置为true时进度条会马上消失。

o    void setFailure(Throwable throwable) 图片加载失败的回调,可以用来显示失败提示图片。参数为加载失败时抛出的异常。

o    void setRetry(Throwable throwable) 图片加载失败但是还希望再次尝试加载时的回调,可以用来显示重试提示图片。参数为加载失败时抛出的异常。

o    void setControllerOverlay(Drawable drawable) 由于特殊原因要用指定图层覆盖已有图层时的回调,传入的参数会显示在ArrayDrawable的最上方。

这几个函数都会在后面起到比较大的作用。
在接下来的内容中会介绍本节的主角:GenericDraweeHierarchy。它实现了SettableHierarchy接口,你可以从这个类中看到大部分Fresco处理图层的逻辑。

2 图层封装者 - GenericDraweeHierarchy

请记住这句话:GenericDraweeHierarchy只是负责装载每个图层信息的载体。如果你直接使用它去显示图片,那就意味着你将放弃Fresco提供的加载与缓存机制。你可以认为这么做之后SimpleDraweeView就退化成了一个简单的ImageView,只会将ArrayDrawable中的所有设置的图片按顺序显示出来。具体的细节我们将在Fresco源码分析(3) - DraweeView显示图层树中讨论。

首先看几个成员变量:

1

2

3

4

5

6

7

8

9

10

11

12

/* 占位图层 */

private final int mPlaceholderImageIndex;

/* 进度条图层 */

private final int mProgressBarImageIndex;

/* 目标显示图层 */

private final int mActualImageIndex;

/* 重试图层 */

private final int mRetryImageIndex;

/* 失败图层 */

private final int mFailureImageIndex;

/* 控制覆盖图层 */

private final int mControllerOverlayIndex;

简洁明了有木有!没错,这个GenericDraweeHierarchy就是封装与维护Drawable层次的家伙!你需要牢记以上这六种图层名字,它是Fresco的视图显示中最主要的六个图层。

2.1 建造者模式

如果你经常使用Fresco,你就会发现它的设计之中充斥着建造者模式。由于Fresco中的对象初始化经常是比较复杂的,建造者模式能为开发者在创建实例上省去很多功夫。

1GenericDraweeHierarchy的建造者是GenericDraweeHierarchyBuilder。它内部维持着许多图层属性,主要有这两种:

o    默认的变量(渐变动画时间、ScaleType)

o    程序的Resources实例

o    圆角矩形容器的一些参数

o    要放到每个图层容器中的Drawable实例及图层要应用的ScaleType、Matrix等等。

这个Builder内部有大量的getter与setter,你可以为每个图层指定Drawable、ScaleType,以及目标显示图层还可以设置Matrix、Focus(配合ScaleType为FOCUS_CROP时使用)、ColorFilter。

2.2 初始化图层

我们来看一下GenericDraweeHierarchy,从中能够理解Fresco是怎么初始化图层的。

1

2

3

4

5

6

7

8

9

10

GenericDraweeHierarchy(GenericDraweeHierarchyBuilder   builder) {

  mResources = builder.getResources();

  // 获取圆角参数

  mRoundingParams =   builder.getRoundingParams();.

  // 初始化图层数为0

  int numLayers = 0;

  int numBackgrounds = (builder.getBackgrounds() != null) ? builder.getBackgrounds().size() : 0;

  int backgroundsIndex = numLayers;

  numLayers += numBackgrounds;

在这段代码中我们可以看到最开始初始化的是背景图层(顶层图层),会根据是否传入背景图层来判断图层数是否增减。再接着往下看:

1

2

3

4

5

6

7

8

9

10

11

12

Drawable   placeholderImageBranch = builder.getPlaceholderImage();

if   (placeholderImageBranch == null) {

  placeholderImageBranch =   getEmptyPlaceholderDrawable();

}

placeholderImageBranch   = maybeApplyRoundingBitmapOnly(

    mRoundingParams,

    mResources,

    placeholderImageBranch);

placeholderImageBranch   = maybeWrapWithScaleType(

    placeholderImageBranch,

    builder.getPlaceholderImageScaleType());

mPlaceholderImageIndex   = numLayers++;

在这段代码中,它对占位图层进行了以下处理:

o    获取图层Drawable资源,如果没有设置,它将创建一个透明图层。

o    根据圆角参数对图片进行圆角处理。

o    将待显示的Drawable资源包装进一个ScaleTypeDrawable中,处理缩放逻辑(关于ScaleTypeDrawable可以参考Fresco源码分析(1) - 图像层次与各类Drawable)。

o    记录图层在ArrayDrawable中的index,图层数量加一。

我们再看看目标显示图层的处理逻辑,与占位图层的处理有什么区别:

1

2

3

4

5

6

7

8

9

10

11

12

Drawable   actualImageBranch = null;

mActualImageSettableDrawable   = new SettableDrawable(mEmptyActualImageDrawable);

actualImageBranch   = mActualImageSettableDrawable;

actualImageBranch   = maybeWrapWithScaleType(

    actualImageBranch,

    builder.getActualImageScaleType(),

    builder.getActualImageFocusPoint());

actualImageBranch   = maybeWrapWithMatrix(

    actualImageBranch,

    builder.getActualImageMatrix());

actualImageBranch.setColorFilter(builder.getActualImageColorFilter());

mActualImageIndex   = numLayers++;

与占位图层有区别的是它在显示图上多加了一了SettableDrawable容器(正常图层只有一个ScaleTypeDrawable容器),没有进行圆角处理。由于可以后续改变图像内容,它直接使用了默认的透明图来初始化图层,而且它还拥有ColorFilter、Matrix等特权。

需要注意的一点:在不显式指定图层内容的时候,占位图层、目标显示图层、控制覆盖图层将会创建透明图层实例,其他图层不会创建实例。 并且只有在指定内容的时候图层数量才会增加,除此以外其他图层与占位图、目标图层的初始化没有什么区别。

在初始化完基本图层之后,那我们接着看余下初始化过程:

1

2

3

4

5

6

7

8

9

//   overlays

int   overlaysIndex = numLayers;

int   numOverlays =

    ((builder.getOverlays() != null) ?   builder.getOverlays().size() : 0) +

        ((builder.getPressedStateOverlay() !=   null) ? 1 : 0);

numLayers   += numOverlays;

//   controller overlay

mControllerOverlayIndex   = numLayers++;

这部分是初始化覆盖图层及控制覆盖图层。如果没有设置覆盖图层,他们不会被初始化。不过控制覆盖图层是会被初始化成透明图层的。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

//   array of layers

Drawable[]   layers = new Drawable[numLayers];

if   (numBackgrounds > 0) {

  int index = 0;

  for (Drawable background :   builder.getBackgrounds()) {

    layers[backgroundsIndex + index++] =

        maybeApplyRoundingBitmapOnly(mRoundingParams,   mResources, background);

  }

}

if   (mPlaceholderImageIndex >= 0) {

  layers[mPlaceholderImageIndex] =   placeholderImageBranch;

}

// 各图层赋值

if   (mFailureImageIndex >= 0) {

  layers[mFailureImageIndex] = failureImageBranch;

}

if   (numOverlays > 0) {

  int index = 0;

  if (builder.getOverlays() != null) {

    for (Drawable overlay :   builder.getOverlays()) {

      layers[overlaysIndex + index++] =   overlay;

    }

  }

  //按下时加在图片上的的覆盖图层

  if (builder.getPressedStateOverlay() !=   null) {

    layers[overlaysIndex + index++] =   builder.getPressedStateOverlay();

  }

}

if   (mControllerOverlayIndex >= 0) {

  layers[mControllerOverlayIndex] =   mEmptyControllerOverlayDrawable;

}

这段代码的意思也是简明易懂,它新建个Drawable数组,将存在的图层依次向里面添加。如果你了解了上一章的内容,你自然知道接下来做的是什么:用这个数组来初始化FadeDrawable,初始化整个视图层次。接着对图层做圆角处理(需要的话)。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

  // 初始化FadeDrawable

  mFadeDrawable = new FadeDrawable(layers);

  mFadeDrawable.setTransitionDuration(builder.getFadeDuration());

  // 圆角处理

  Drawable maybeRoundedDrawable =

        maybeWrapWithRoundedOverlayColor(mRoundingParams, mFadeDrawable);

  // 包装成RootDrawable实例

  mTopLevelDrawable = new   RootDrawable(maybeRoundedDrawable);

  mTopLevelDrawable.mutate();

  resetFade();

}

在resetFade()中将占位图 、背景图层、覆盖图层显示出来。

2.3 需要注意的一点:一个Drawable实例只能与一个DraweeHierarchy绑定!

如果绑定了多个DraweeHierarchy,会出问题。由于在初始化过程中它将Drawable数组赋值给FadeDrawable,而FadeDrawable又继承于ArrayDrawable,它会在初始化的时候为每个数组Drawable的Drawable.Callback设置为自己,若是TransfromAwareDrawable的话还会设置自己为TransformCallback。而我们可以在它的setDrawable(int index, Drawable drawable)函数中看到这么一段代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

if (drawable != mLayers[index]) {

  if (mIsMutated) {

    drawable = drawable.mutate();

  }

  DrawableUtils.setCallbacks(mLayers[index], null, null);

  DrawableUtils.setCallbacks(drawable, null, null);

    DrawableUtils.setDrawableProperties(drawable, mDrawableProperties);

  DrawableUtils.copyProperties(drawable,   mLayers[index]);

  DrawableUtils.setCallbacks(drawable, this, this);

  mIsStatefulCalculated = false;

  mLayers[index] = drawable;

  invalidateSelf();

}

可以看出,在替换图片时,它会将原来Drawable的这些回调都设置为null。由此很可能会导致Bug,请参考我的一篇翻译文章:Android LayerDrawable 和 Drawable.Callback

3 类图

由于类中方法、变量过多,作者对其做了大量精简,仅用于参考设计层次。

原文链接:http://www.apkbus.com/blog-705730-61782.html

0人推荐
随时随地看视频
慕课网APP