解耦项目中的图片加载库

在移动开发中,我们通常会引入一些第三方库来构件我们的应用框架,而为了使用方便,我们通常会对其进行二次封装,最典型的莫过于网络框架了(如OkHttp, Vollay, Retrofit等)。当然,作为重要组成部分的图片加载库,我们也应用对其进行二次封装。

背景

虽然在开发之前,我们会谨慎选择第三方库,但随着未来需求的不断迭代,替换开源库似乎也是可能的。在这种情况下,如果之前没有对图片加载库进行封装,那么在替换时将会发现这是一项艰巨的任务,毕竟用到的页面太多。为了简化这项任务,我们可以对图片加载库进行简单的封装,从而降低图片加载库与项目的耦合度。

分析

现在开源社区最受欢迎的图片加载库莫过于Fresco, Universal ImageLoader, Glide, Picasso。对于这几个库的区别和性能这里不做赘述,可自行Google。这里我们就以这四种图片加载库进行分析,我们会发现,Fresco是以View的形式存在的,而其他三种都是单独的组件。但是图片的加载肯定离不开ImageView。既然这样,那我们可以通过自定义一个View来对图片加载库进行封装。

需求

对于图片加载库的使用,莫过于以下场景:

  1. 设置图片加载过程中显示的图片;
  2. 设置图片加载失败时的图片;
  3. 设置图片的显示方式;
  4. 设置图片的形状;
  5. 设置图片边框,颜色以及圆角角度;
  6. 下载图片并进行监听;

目的

无论使用哪种图片加载库,我们只需要修改这个封装的View内容,而不需要修改其他地方。

实现

下面我们以Fresco作为图片加载库,对其进行简单的封装。Fresco对应的View为SimpleDraweeView,其继承自ImageView, 这里我们通过继承SimpleDraweeView来实现(PS: 对于其他的图片加载库,可直接继承ImageView)

public class NetworkImageView extends SimpleDraweeView {
    ...

       public NetworkImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 通过自定义属性解决不同加载库之间的差异
        TypedArray arrays = context.obtainStyledAttributes(attrs, R.styleable.NetworkImageView);
        mPlaceHolder = arrays.getResourceId(R.styleable.NetworkImageView_placeHolder, 0);
        mErrorHolder = arrays.getResourceId(R.styleable.NetworkImageView_errorHolder, 0);
        mShape = arrays.getInt(R.styleable.NetworkImageView_shape, SHAPE_NORMAL);
        mRadius = arrays.getDimensionPixelSize(R.styleable.NetworkImageView_radius, 0);
        mBorderWidth = arrays.getDimensionPixelSize(R.styleable.NetworkImageView_shapeBorderWidth, 0);
        mBorderColor = arrays.getColor(R.styleable.NetworkImageView_borderColor, Color.WHITE);
        arrays.recycle();
    }  
    ...
}  

为不同的加载库提供相同的调用接口,这样不同的图片加载库只是displayImage的内部实现不同,调用过程却是一致的,大大降低了图片加载库的耦合度。

public void displayImage(String url, int placeHolder, int failureImage, ScaleType scaleType) {
    RoundingParams roundingParams = null;
    if (mShape == SHAPE_CIRCLE) {
        roundingParams = RoundingParams.asCircle();
        roundingParams.setBorder(mBorderColor, mBorderWidth);
    } else if (mShape == SHAPE_ROUND){
        roundingParams = RoundingParams.fromCornersRadius(mRadius);
        roundingParams.setBorder(mBorderColor, mBorderWidth);
    } else {
        /**
         * Nothing
         */
    }
    GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(getResources());
    if (placeHolder != 0) {
        builder.setPlaceholderImage(placeHolder);
    }
    if (failureImage != 0) {
        builder.setFailureImage(failureImage);
    }
    if (mShape != SHAPE_NORMAL) {
        builder.setRoundingParams(roundingParams);
    }
    GenericDraweeHierarchy hierarchy = builder.build();
    setScaleType(hierarchy, scaleType);
    setHierarchy(hierarchy);
    setImageURI(Uri.parse(url));
}

当然,使用过程中少不了下载监听,这里我们提供统一的接口,来解决不同加载库监听接口不一致的问题。

/**
 * 加载监听
 *
 * 只告诉客户端图片是否加载完成或加载失败,不会返回任何信息
 */
public interface LoadingListener {
    void loadComplete();
    void loadFailure();
}

/**
 * 图片下载监听
 *
 * 返回下载图片的bitmap
 */
public interface DownloadListener {
    void downloadFinish(Bitmap bitmap);
    void downloadFailure();
}

这里提供了两个接口来满足不同的需求,开发者只需要在所使用的图片加载库的下载监听中调用相应的接口即可。

对于其他的细节,如图片的形状,显示方式,下载过程等可参考源码。

源码

NetworkImageView

使用

 <com.zhinanmao.znm.view.NetworkImageView
    android:id="@+id/user_icon"
    android:layout_width="65dp"
    android:layout_height="65dp"
    app:shape="circle"
    app:placeHolder="@drawable/default_bg_300_300"
    app:errorHolder="@drawable/default_bg_300_300"/>

userIcon.displayImage(icon_url);

总结

本篇文章只是提供一种降低图片加载库耦合度的思路, 其目的是使第三方库可进行快速替换。如您在阅读本文时发现不妥之处或更好的建议,欢迎指正。

,