继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Fresco源码分析(1) - 图像层次与各类Drawable

慕妹3146593
关注TA
已关注
手记 107
粉丝 14
获赞 39

首先介绍几种Fresco中的图像层次,了解它们会帮助你理解Fresco加载图像的原理。

1 引论:给图像分层次是什么作用?

如果你使用过Fresco这个强大的库之后,你就知道它可以在一个图像的加载、绘制过程中实现极大的定制化。你可以设置进度条来显示图片加载/下载的进度,可以设置占位图等到图片加载/下载成功后再显示目标图片,可以让在加载/下载失败后显示失败图片(更多功能参考Fresco中文文档)。Fresco将进度条、占位图、失败图都作为图像的一层视图来管理,这部分仅仅负责视图层次绘制,将负责视图功能部分与逻辑部分尽可能实现解耦。

Fresco中定义了许多Drawable,它们都直接或间接继承了Drawable,但是各自的功能是不一样的。经过总结,我认为其中一共有三种功能的Drawable:层次型、容器型和视图型。直接上源码的一个视图例子就好理解了,作者进行了适当修改与翻译。

o 层次型Drawable(维持图层)
|
—— 容器型Drawable(可对内容进行缩放)
|    |
|    — 视图型Drawable(存放占位图)
|
—— 容器型Drawable(可对内容进行缩放)
|    |
|    —– 容器型Drawable(可多次设置内容)
|       |
|       — 视图型Drawable(存放目标显示图片)
|
—— 容器型Drawable(可对内容进行缩放)
|    |
|    — 视图型Drawable(存放重试图片)
|
—— 容器型Drawable(可对内容进行缩放)
    |
    — 视图型Drawable(存放失败图片)

该例位于com.facebook.drawee.generic.GenericDraweeHierarchy的类注释中。

这个例子充分描述了一个图像的层次,当然也可以在设置的时候往里面自行设置所需要的图层。

2 层次型Drawable

在这一节中介绍的Drawable并不直接负责具体图像绘制,而是负责组建图像层次。

2.1 ArrayDrawable

ArrayDrawable内部存储着一个Drawable数组,它与Android内置的LayerDrawable很相似,可见它将数组中的Drawable当做它的图层,在绘制的时候ArrayDrawable会按照数组顺序绘制其中的图层,数组最后的成员会显示在最上方。不过与LayerDrawable最大的不同的点有两处:

o    绘制顺序虽然是数组顺序,但是ArrayDrawable在绘制时会跳过暂时不需要绘制的图层;

o    在ArrayDrawable中不支持动态的添加/删除图层,只能在初始化时通过传入的数组决定图层数。不过好在它能够为存在的图层更换Drawable。(关于LayerDrawable可以参考我翻译的一文章:Android LayerDrawable。)

2.2 FadeDrawable

FadeDrawable继承了ArrayDrawable。它除了具有ArrayDrawable本身的功能之外,还提供隐藏/显示图层的功能(可设置渐变)。具体的几个核心函数有:

o    setTransitionDuration(int durationMs) 设置隐藏/显示图层渐变动画时间(默认为300ms)。

o    fadeInLayer(int index) 显示指定图层

o    fadeOutLayer(int index) 隐藏指定图层

o    fadeInAllLayers() 显示所有图层

o    fadeOutAllLayers() 隐藏所有图层

o    fadeToLayer(int index) 显示指定图层同时隐藏其他图层

o    fadeUpToLayer(int index) 隐藏数组下标<=index的图层

它内部维护着一个boolean数组来维持需要显示的图层(可以调用isLayerOn(int inxex)查看指定图层是否显示)。

3 容器型Drawable

ForwardingDrawable

ForwardingDrawable通俗的来说就是图片容器。它内部维护一个Drawable变量mCurrentDelegate,将Drawable的基本函数以及一些回调函数传递给目标图片,并在draw(Canvas)函数中调用mCurrentDeletate.draw(Canvas)函数将目标图片绘制出来。
它可以通过getCurrent()来获取容器内容,起到一个相当于是传递树的作用。它是所有容器型Drawable的基类,以下介绍几个它的子类,他们实现了不同功能的容器包装。

ScaleTypeDrawable

ScaleTypeDrawable封装了对代理图片的缩放处理,具体的缩放参数(ScaleType)与Android ScaleType的名字、功能相同。它在处理图片缩放的时候与ImageView的处理方式相似,我们来看一下它是怎么处理的:

1

2

3

4

5

6

7

private void configureBoundsIfUnderlyingChanged() {

  /* 当图像尺寸改变后,就重新确定图像边缘 */

  if (mUnderlyingWidth != getCurrent().getIntrinsicWidth() ||

      mUnderlyingHeight !=   getCurrent().getIntrinsicHeight()) {

    configureBounds();

  }

}

其中mUnderLyingWidth与mUnderLyingHeight维护了已知的上一张图片宽高(初始均为0),当要绘制时调用这个函数,如果与上一张绘制的图像不一样时,就重新确认绘制边缘与缩放矩阵。

再来看一下configureBounds()是怎么确定转换矩阵的吧:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

void configureBounds() {

   ...

   // 特殊情况判断:绘制图片是否为空白图或与之前绘制的图片尺寸相同

   ...

   // 当要往X、Y方向上填充容器时,直接将目标图片边界设置成容器图片的边界即可,Drawable会在绘制的时候自己调整。

   if (mScaleType == ScalingUtils.ScaleType.FIT_XY) {

     underlyingDrawable.setBounds(bounds);

     mDrawMatrix = null;

     return;

   }

   //处理其他缩放情况

   underlyingDrawable.setBounds(0, 0, underlyingWidth, underlyingHeight);

   ScalingUtils.getTransform(

       mTempMatrix,

       bounds,

       underlyingWidth,

       underlyingHeight,

       (mFocusPoint != null) ? mFocusPoint.x : 0.5f,

       (mFocusPoint != null) ? mFocusPoint.y : 0.5f,

       mScaleType);

   mDrawMatrix = mTempMatrix;

 }

这里通过ScalingUtils.getTransform来计算出变换矩阵,我们以CENTER_INSIDE为例研究一下它的工作机制:

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

public static Matrix getTransform(

      final Matrix transform,

      final Rect parentBounds,

      final int childWidth,

      final int childHeight,

      final float focusX,

      final float focusY,

      final ScaleType scaleType) {

    ...

    final float scaleX = (float) parentWidth / (float) childWidth;

    final float scaleY = (float) parentHeight / (float) childHeight;

    ...

    switch(scaleType){

                 ...

                 case CENTER_INSIDE:

                         //计算缩放倍数

                              scale = Math.min(Math.min(scaleX,   scaleY), 1.0f);

                              //计算平移距离

                         dx = parentBounds.left + (parentWidth   - childWidth * scale) * 0.5f;

                         dy = parentBounds.top + (parentHeight   - childHeight * scale) * 0.5f;

                         //设置缩放矩阵

                         transform.setScale(scale, scale);

                         //设置评议距离

                         transform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));

                          break;

         }

         return transform;

}

其他具体的ScaleType处理就不赘述了,有兴趣的同学可以自己看源码再研究。默认的ScaleType是CENTER_CROP。

在计算好矩阵之后,我们来看一下这个容器是怎么将它的内容绘制出来的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

public void draw(Canvas canvas) {

  //计算矩阵

  configureBoundsIfUnderlyingChanged();

  if (mDrawMatrix != null) {

    int saveCount = canvas.save();

    canvas.clipRect(getBounds());

    canvas.concat(mDrawMatrix);

    super.draw(canvas);

    canvas.restoreToCount(saveCount);

  } else {

    //无变换矩阵时,直接让Drawable绘制到确定的边缘中。

    super.draw(canvas);

  }

}

我们可以看到它将矩阵应用到Canvas中,并调用ForwardingDrawable的draw(Canvas)让它将目标视图绘制出来,之后还原Canvas的缩放属性防止累加缩放。

暂时先介绍各类容器Drawable的功能,为方便后续理解。待分析完Fresco的架构之后再为分析。

o    SettableDrawable:可以多次设置内容Drawable的容器,多用在目标图片的图层中。

o    AutoRotateDrawable:提供内容动态旋转的容器。

o    OrientedDrawable:可以将内容Drawable以一个特定的角度绘制的容器。

o    MatrixDrawable:可以为内容应用变形矩阵的容器,它只能赋予给显示目标图片的那个图层。不能在一个图层上同时使用MatrixDrawable与ScaleTypeDrawable!

o    RoundedCornersDrawable:可以将内容的边界修剪成圆角矩形(目前版本暂不支持)或用实心的圆角矩形覆盖内容的容器。

o    GenericDraweeHierarchy.RootDrawable:专门用于顶层图层的容器。

4 视图型Drawable

大多数情况下,Fresco用于表现图片的视图型Drawable使用的就是Android原生Drawable来做图像的载体。不过也有两个例外:

ProgressBarDrawable

ProgressBarDrawable是负责绘制进度条的Drawable。它内部维持一个level用来描述进度(0<=level<=10000),并自己实现了绘制过程,我们首先通过源码来看一下是怎么绘制的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

@Override

public void draw(Canvas canvas) {

  if (mHideWhenZero && mLevel == 0) {

    return;

  }

  drawBar(canvas, 10000, mBackgroundColor);

  drawBar(canvas, mLevel, mColor);

}

private void drawBar(Canvas canvas, int level, int color) {

  Rect bounds = getBounds();

  int length = (bounds.width() - 2 * mPadding) * level / 10000;

  int xpos = bounds.left + mPadding;

  int ypos = bounds.bottom - mPadding - mBarWidth;

  mPaint.setColor(color);

  canvas.drawRect(xpos, ypos, xpos + length,   ypos + mBarWidth, mPaint);

}

可以看出,它先将整个进度条填充满backgroundColor颜色(可以通过setBackgroundColor设置),再将进度覆盖区域矩形填充满color颜色(可以通过setColor设置)。

RoundedBitmapDrawable

这个Drawable与上面的容器型RoundedCornersDrawable有几个区别:

RoundedBitmapDrawable是将自身内容修剪成圆角矩形边绘制出来,并且可以使用Bitmap作为对象,返回一个BitmapDrawable。而RoundedCornersDrawable是将容器内容修剪成圆角矩形边,并且可以选择是否用指定颜色覆盖容器内容,可以使用任何Drawable当做容器。

待分析完Fresco的架构之后,会回来分析圆角图片的实现机制。它与正常的使用Xfermode实现方式不同。

实际上这两个类的功能是有一定重合的,我认为是由于RoundedCornerDrawable目前只能做到用圆角矩形覆盖内容,而无法将内容修剪成圆角矩形,所以才使用了RoundedBitmapDrawable。关于RoundedCornersDrawable的功能Fresco也在改进中。期待后续它能将两个功能合并起来。

5 特殊Drawable - TransformAwareDrawable 和 VisibilityAwareDrawable

为什么说它们特殊呢,因为他们只是接口!

TransformAwareDrawable要和TransfromCallback一起使用。TransformAwareDrawable的作用很简单,就是提供设置TransfromCallback的回调函数,那我们来看看TransfromCallback的作用是什么:

1

2

3

4

5

6

7

8

public   interface TransformCallback {

  // 获取已经应用在自身的变换Matrix,储存在transfrom中。

  public void getTransform(Matrix transform);

  // 获取根节点边界,储存在bounds中。

  public void getRootBounds(RectF bounds);

}

之所以要设置这个回调,是因为本篇中的Drawable是有层次的。如果B 是 A的子图层,那应用在A上的变换矩阵自然应该应用到B上,所以提供这个回调可以让B获取应用在A上的变换矩阵,从而正确地进行绘制。

在本篇文章中出现的所有Drawable都实现了TransformAwareDrawableTransfromCallback 在ArrayDrawable中的getTransfrom中可以看出它的工作机制(实际上除了个别自身有缩放的图层如ScaleTypeDrawable, MatrixDrawable外的实现都是想以下这段代码一样):

1

2

3

4

5

6

7

8

9

10

@Override

public void getTransform(Matrix transform) {

  //如果有父图层,则获取应用在父图层上的变换矩阵

  if (mTransformCallback != null) {

      mTransformCallback.getTransform(transform);

  } else {

  //如果没有父图层,就获取单位矩阵

    transform.reset();

  }

}

在ScaleTypeDrawable和MatrixDrawable中会将自身的变换矩阵通过Matrix.confat(Matrix m)传给transform。如此一来就实现了变换矩阵向下传递的功能。

VisibilityAwareDrawable与VisibilityCallback搭配使用,它提供了在自身可见度改变的时候的通知函数(onVisibilityChange(boolean visible))和在自身绘制时发生通知的回调(onDraw())。仅仅GenericDraweeHierarchy.RootDrawable实现了它。

6 类图

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

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

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP