Fresco图片显示原理浅析

(第一篇)Fresco架构设计赏析

(第二篇)Fresco缓存架构分析

本文是Fresco源码分析系列第三篇文章,主要来分析一下Fresco UI层的实现,包括下面这些点:

  1. 图片显示原理,图片加载过程中各个阶段的图片切换原理。(比如由占位图->目标图片)
  2. 圆角的实现
  3. ScaleType的实现

图片显示原理与多状态切换逻辑

Fresco中负责图片展示工作的是DraweeHierarchy,它内部维护着一个Drawable序列,在图片加载过程中的不同阶段可以显示不同状态的Drawable

图片显示原理

我们一般直接使用SimpleDraweeView,它继承自DraweeView:

DraweeView.java

public class DraweeView extends ImageView {

    public void setController(@Nullable DraweeController draweeController) {
        mDraweeHolder.setController(draweeController);
        super.setImageDrawable(mDraweeHolder.getTopLevelDrawable()); 
    }
}

在调用SimpleDraweeView.setImageUri()时会调用到DraweeView.setController(),即此时是直接显示的mDraweeHolder.getTopLevelDrawable():

DraweeHolder.java

public @Nullable Drawable getTopLevelDrawable() {
    return mHierarchy == null ? null : mHierarchy.getTopLevelDrawable();
}

所以最终的显示的DrawablemHierarchy.getTopLevelDrawable()mHierarchy的实现是GenericDraweeHierarchymHierarchy.getTopLevelDrawable()获取的Drawable实际上可以理解为FadeDrawable:

GenericDraweeHierarchy.java

    GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) {
        mFadeDrawable = new FadeDrawable(layers);
        Drawable maybeRoundedDrawable = WrappingUtils.maybeWrapWithRoundedOverlayColor(mFadeDrawable, mRoundingParams);
        mTopLevelDrawable = new RootDrawable(maybeRoundedDrawable); //RootDrawable 只是一个装饰类
    }

FadeDrawable内部维护着一个Drawable数组,它可以由一个Drawable切换到另一个DrawableDrawable的切换过程中伴有着透明度改变的动画:

public class FadeDrawable extends ArrayDrawable {
    private final Drawable[] mLayers;
    
    @Override
    public void draw(Canvas canvas) {
        ...更新Drawable的透明度

        //从前往后一层一层的画出来
        for (int i = 0; i < mLayers.length; i++) {
            drawDrawableWithAlpha(canvas, mLayers[i], mAlphas[i] * mAlpha / 255);
        }
    }
}

Fresco中一共有多少层Drawable(layer)呢?我们看一下GenericDraweeHierarchy的初始化代码:

GenericDraweeHierarchy.java

    private static final int BACKGROUND_IMAGE_INDEX = 0;
    private static final int PLACEHOLDER_IMAGE_INDEX = 1;
    private static final int ACTUAL_IMAGE_INDEX = 2;
    private static final int PROGRESS_BAR_IMAGE_INDEX = 3;
    private static final int RETRY_IMAGE_INDEX = 4;
    private static final int FAILURE_IMAGE_INDEX = 5;
    private static final int OVERLAY_IMAGES_INDEX = 6;

    GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) {
        Drawable[] layers = new Drawable[numLayers];  // 一般是6层
        layers[BACKGROUND_IMAGE_INDEX] = ...
        layers[PLACEHOLDER_IMAGE_INDEX] = ...
        layers[ACTUAL_IMAGE_INDEX] = ...
        layers[PROGRESS_BAR_IMAGE_INDEX] = ...
        layers[RETRY_IMAGE_INDEX] = ...
        layers[FAILURE_IMAGE_INDEX] = ...
        ...这里还有一个overlayer层
    }

即在构造GenericDraweeHierarchy就确定了有几层Drawable(FresconumLayers的值一般为6)。当然如果没有这一层Drawable(比如没有提供Progress Drawable),那么这一层Drawable就是null。通过FadeDrawable.draw()已经知道会按照顺序把这些Drawable都画出来(Drawable为null的话就不会画, 透明度为0也不会画)。

可以看到我们实际上要显示的图片位于第3层级。那么如果图片加载完成,如何从加载进度的Drawable切换到实际的图片呢?:

GenericDraweeHierarchy.java

public void setImage(Drawable drawable, float progress, boolean immediate) {
    drawable = WrappingUtils.maybeApplyLeafRounding(drawable, mRoundingParams, mResources); //包裹上圆形参数
    ...
    fadeOutBranches();
    fadeInLayer(ACTUAL_IMAGE_INDEX);
    ...
}

private void fadeOutBranches() {
    fadeOutLayer(PLACEHOLDER_IMAGE_INDEX);
    fadeOutLayer(ACTUAL_IMAGE_INDEX);
    fadeOutLayer(PROGRESS_BAR_IMAGE_INDEX);
    fadeOutLayer(RETRY_IMAGE_INDEX);
    fadeOutLayer(FAILURE_IMAGE_INDEX);
}

当加载完成完最终图片后就会调用到GenericDraweeHierarchy.setImage(),上面逻辑其实涉及到的代码很多,但是逻辑很简单就不深入看了。上面的两个核心方法可以这样理解:

  • fadeOutLayer() : 把这一层Drawable(layer)的透明度设置为0
  • fadeInLayer() : 把这一层的透明度设置为1

到这里,基本上就叙述了Fresco的图片显示原理。其实整体流程可以用下图表示:

Fresco图片显示原理浅析_第1张图片
Fresco图片显示原理.png

圆角的实现

直接来看具体的实现代码:

WrappingUtils.java

private static Drawable applyLeafRounding(Drawable drawable, RoundingParams roundingParams, Resources resources) {
    if (drawable instanceof BitmapDrawable) {
        final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
        RoundedBitmapDrawable roundedBitmapDrawable = new RoundedBitmapDrawable(resources,bitmapDrawable.getBitmap(),bitmapDrawable.getPaint());
        applyRoundingParams(roundedBitmapDrawable, roundingParams);
        return roundedBitmapDrawable;
    }
    ...
    return drawable;
}

RoundedBitmapDrawable.java

    @Override
    public void draw(Canvas canvas) {
        if (!shouldRound()) {
            super.draw(canvas);
            return;
        }
        ...
        updatePath(); //更新圆角path or 圆形path
        updatePaint();
        ...
        canvas.drawPath(mPath, mPaint);
    }

    private void updatePaint() {
        if (mLastBitmap == null || mLastBitmap.get() != mBitmap) {
            mLastBitmap = new WeakReference<>(mBitmap);
            mPaint.setShader(new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
            mIsShaderTransformDirty = true;
        }
        ...
    }

Fresco的圆角实际上是使用BitmapShader来实现的。

ScaleType的实现

FrescoScaleType的实现原理其实和ImageView是相同的。但由于SimpleDraweeView内部是维护了多个Drawable,所以它并不能直接使用ImageView的实现方式,它需要把它维护的每一个Drawable都做对应的ScaleType操作。我们先来看一下ImageViewScaleType的实现:

ImageView ScaleType的实现

ImageView中 CENTER_CROP 的实现

    private void configureBounds() {
        //drawable 的宽高
        final int dwidth = mDrawableWidth;  
        final int dheight = mDrawableHeight;

        //当前view的宽高
        final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
        final int vheight = getHeight() - mPaddingTop - mPaddingBottom;

        ...
        if (ScaleType.CENTER_CROP == mScaleType) {
            mDrawMatrix = mMatrix;

            float scale;
            float dx = 0, dy = 0;

            if (dwidth * vheight > vwidth * dheight) {
                scale = (float) vheight / (float) dheight;
                dx = (vwidth - dwidth * scale) * 0.5f;
            } else {
                scale = (float) vwidth / (float) dwidth;
                dy = (vheight - dheight * scale) * 0.5f;
            }

            mDrawMatrix.setScale(scale, scale);
            mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy)); // 从哪个坐标开始画 Drawable
        }
        ...
    }

Fresco的实现

Fresco中如果对图片设置了ScaleType,那么就会把对应的Drawable封装为ScaleTypeDrawable, 它的draw():

    public void draw(Canvas canvas) {
        // 这个方法类似于 ImageView configureBounds的实现, 配置了 mDrawMatrix
        configureBoundsIfUnderlyingChanged(); 
        if (mDrawMatrix != null) {
            int saveCount = canvas.save();
            canvas.clipRect(getBounds());
            canvas.concat(mDrawMatrix);
            super.draw(canvas);
            canvas.restoreToCount(saveCount);
        } else {
            super.draw(canvas);
        }
    }

欢迎关注我的Android进阶计划看更多干货

欢迎关注我的微信公众号:susion随心

Fresco图片显示原理浅析_第2张图片
微信公众号.jpeg

你可能感兴趣的:(Fresco图片显示原理浅析)