Fresco图片显示原理浅析

作者 : 开心源码 本文共5408个字,预计阅读时间需要14分钟 发布时间: 2022-05-12 共204人阅读

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

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

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

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

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

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

图片显示原理

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

DraweeView.java

public class DraweeView<DH extends DraweeHierarchy> 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图片显示原理.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随心

微信公众号.jpeg

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Fresco图片显示原理浅析

发表回复