内容简介:项目开发中,往往会随着需求的改变而切换到其它图片加载框架上去。如果最初代码设计的耦合度太高,那么恭喜你,成功入坑了。至今无法忘却整个项目一行行去复制粘贴被支配的恐惧。:)那么是否存在一种方式 能够一劳永逸地解决这个痛点呢?下面我们来分析一下图片加载框架面对的现状和解决思路。
项目开发中,往往会随着需求的改变而切换到其它图片加载框架上去。如果最初代码设计的耦合度太高,那么恭喜你,成功入坑了。至今无法忘却整个项目一行行去复制粘贴被支配的恐惧。:)
那么是否存在一种方式 能够一劳永逸地解决这个痛点呢?下面我们来分析一下图片加载框架面对的现状和解决思路。
问题现状
一个优秀的框架一般在代码设计的时候已经封装很不错了,对于开发者而言框架的使用也是很方便,但是为什么说我们往往还要去做这方面的框架封装呢?原因很简单,实际项目开发中,我们不得不面对着日新月异的需求变化,想要在这个变化中最大程度的实现代码的可扩展性和变通性(当然还可以偷懒),不能因为牵一发而动全身,同时要将框架适配到实际项目,框架的再封装设计显得尤为重要。
不多废话,我们可以开始今天的图片封装之路了。
设计思路
图片框架的封装主要需要满足以下三点:
-
低耦合,方便将来的代码扩展。至少要支持目前市场上使用率最高的图片框架Fresco、Glide、Picasso三者之间的切换
-
满足项目中各种需求
-
调用方便
谈到图片封装,最先想到的是把一些常用的功能点作为参数传入到方法内,然后调用图片加载框架实现我们图片的加载工作。比如说像下面这样
public interface ImageLoader { void loadImage(ImageView view, String path, int placeholderId, int errorId,boolean skipMemory); void loadImage(ImageView view, File file, int placeholderId, int errorId, boolean skipMemory); } 复制代码
然后分别写对应的ImageLoader实现类FrescoImageLoader、GlideImageLoader、PicassoImageLoader,最后采用策略的 设计模式 实现代码的切换。那么这种方式实际效果如何呢?实际开发中很明显的一个 问题就是,对于每一个需要的参数都需要进行对应的封装,就不止上面所提到的两个方法,我们需要封装大量的方法去满足实际的项目需要,而且每个框架的很多属性不一致,如果切换图片框架的话,还是需要大量的切换成本的。
于是我们想到了下面的这种思路
public interface ILoaderStrategy { void loadImage(LoaderOptions options); /** * 清理内存缓存 */ void clearMemoryCache(); /** * 清理磁盘缓存 */ void clearDiskCache(); } 复制代码
提取各个框架通用的View,path/file文件路径,通过LoaderOptions解决大量不同参数传入的问题。这里需要说明的是,LoaderOptions中采用控件View,而不是ImageView,主要考虑到Fresco图片框架采用了DraweeView,这里保留了设计的扩展性。而图片参数类LoaderOptions采用了Builder设计模式:
public class LoaderOptions { public int placeholderResId; public int errorResId; public boolean isCenterCrop; public boolean isCenterInside; public boolean skipLocalCache; //是否缓存到本地 public boolean skipNetCache; public Bitmap.Config config = Bitmap.Config.RGB_565; public int targetWidth; public int targetHeight; public float bitmapAngle; //圆角角度 public float degrees; //旋转角度.注意:picasso针对三星等本地图片,默认旋转回0度,即正常位置。此时不需要自己rotate public Drawable placeholder; public View targetView;//targetView展示图片 public BitmapCallBack callBack; public String url; public File file; public int drawableResId; public Uri uri; public LoaderOptions(String url) { this.url = url; } public LoaderOptions(File file) { this.file = file; } public LoaderOptions(int drawableResId) { this.drawableResId = drawableResId; } public LoaderOptions(Uri uri) { this.uri = uri; } public void into(View targetView) { this.targetView = targetView; ImageLoader.getInstance().loadOptions(this); } public void bitmap(BitmapCallBack callBack) { this.callBack = callBack; ImageLoader.getInstance().loadOptions(this); } public LoaderOptions placeholder(@DrawableRes int placeholderResId) { this.placeholderResId = placeholderResId; return this; } public LoaderOptions placeholder(Drawable placeholder) { this.placeholder = placeholder; return this; } public LoaderOptions error(@DrawableRes int errorResId) { this.errorResId = errorResId; return this; } public LoaderOptions centerCrop() { isCenterCrop = true; return this; } public LoaderOptions centerInside() { isCenterInside = true; return this; } public LoaderOptions config(Bitmap.Config config) { this.config = config; return this; } public LoaderOptions resize(int targetWidth, int targetHeight) { this.targetWidth = targetWidth; this.targetHeight = targetHeight; return this; } /** * 圆角 * @param bitmapAngle 度数 * @return */ public LoaderOptions angle(float bitmapAngle) { this.bitmapAngle = bitmapAngle; return this; } public LoaderOptions skipLocalCache(boolean skipLocalCache) { this.skipLocalCache = skipLocalCache; return this; } public LoaderOptions skipNetCache(boolean skipNetCache) { this.skipNetCache = skipNetCache; return this; } public LoaderOptions rotate(float degrees) { this.degrees = degrees; return this; } } 复制代码
当然了,如果觉得有项目中需要可以以LoderOptions为基类继续扩展LoderOptions,不过现在这样在LoaderOptions上自行扩展基本上可以满足所有日常需要了。现在解决了代码设计的方向,那么接下来 我们要采取策略的方式实现图片框架的解耦。
import android.view.View; import com.squareup.picasso.Callback; import java.io.File; /** * 图片管理类,提供对外接口。 * 静态代理模式,开发者只需要关心ImageLoader + LoaderOptions * Created by MhListener on 2017/6/27. */ public class ImageLoader{ private static ILoaderStrategy sLoader; private static volatile ImageLoader sInstance; private ImageLoader() { } //单例模式 public static ImageLoader getInstance() { if (sInstance == null) { synchronized (ImageLoader.class) { if (sInstance == null) { //若切换其它图片加载框架,可以实现一键替换 sInstance = new ImageLoader(); } } } return sInstance; } //提供实时替换图片加载框架的接口 public void setImageLoader(ILoaderStrategy loader) { if (loader != null) { sLoader = loader; } } public LoaderOptions load(String path) { return new LoaderOptions(path); } public LoaderOptions load(int drawable) { return new LoaderOptions(drawable); } public LoaderOptions load(File file) { return new LoaderOptions(file); } public LoaderOptions load(Uri uri) { return new LoaderOptions(uri); } public void loadOptions(LoaderOptions options) { sLoader.loadImage(options); } public void clearMemoryCache() { sLoader.clearMemoryCache(); } public void clearDiskCache() { sLoader.clearDiskCache(); } } 复制代码
最后我们开始图片加载框架的具体实现方式,这里我实现了Picasso图片加载,开发者可以根据此例自行扩展GlideLoader或者FrescoLoader。
public class PicassoLoader implements ILoaderStrategy { private volatile static Picasso sPicassoSingleton; private final String PICASSO_CACHE = "picasso-cache"; private static LruCache sLruCache = new LruCache(App.gApp); private static Picasso getPicasso() { if (sPicassoSingleton == null) { synchronized (PicassoLoader.class) { if (sPicassoSingleton == null) { sPicassoSingleton = new Picasso.Builder(App.gApp).memoryCache(sLruCache).build(); } } } return sPicassoSingleton; } @Override public void clearMemoryCache() { sLruCache.clear(); } @Override public void clearDiskCache() { File diskFile = new File(App.gApp.getCacheDir(), PICASSO_CACHE); if (diskFile.exists()) { //这边自行写删除代码 // FileUtil.deleteFile(diskFile); } } @Override public void loadImage(LoaderOptions options) { RequestCreator requestCreator = null; if (options.url != null) { requestCreator = getPicasso().load(options.url); } else if (options.file != null) { requestCreator = getPicasso().load(options.file); }else if (options.drawableResId != 0) { requestCreator = getPicasso().load(options.drawableResId); } else if (options.uri != null){ requestCreator = getPicasso().load(options.uri); } if (requestCreator == null) { throw new NullPointerException("requestCreator must not be null"); } if (options.targetHeight > 0 && options.targetWidth > 0) { requestCreator.resize(options.targetWidth, options.targetHeight); } if (options.isCenterInside) { requestCreator.centerInside(); } else if (options.isCenterCrop) { requestCreator.centerCrop(); } if (options.config != null) { requestCreator.config(options.config); } if (options.errorResId != 0) { requestCreator.error(options.errorResId); } if (options.placeholderResId != 0) { requestCreator.placeholder(options.placeholderResId); } if (options.bitmapAngle != 0) { requestCreator.transform(new PicassoTransformation(options.bitmapAngle)); } if (options.skipLocalCache) { requestCreator.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE); } if (options.skipNetCache) { requestCreator.networkPolicy(NetworkPolicy.NO_CACHE, NetworkPolicy.NO_STORE); } if (options.degrees != 0) { requestCreator.rotate(options.degrees); } if (options.targetView instanceof ImageView) { requestCreator.into(((ImageView)options.targetView)); } else if (options.callBack != null){ requestCreator.into(new PicassoTarget(options.callBack)); } } class PicassoTarget implements Target { BitmapCallBack callBack; protected PicassoTarget(BitmapCallBack callBack) { this.callBack = callBack; } @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { if (this.callBack != null) { this.callBack.onBitmapLoaded(bitmap); } } @Override public void onBitmapFailed(Exception e, Drawable errorDrawable) { if (this.callBack != null) { this.callBack.onBitmapFailed(e); } } @Override public void onPrepareLoad(Drawable placeHolderDrawable) { } } class PicassoTransformation implements Transformation { private float bitmapAngle; protected PicassoTransformation(float corner){ this.bitmapAngle = corner; } @Override public Bitmap transform(Bitmap source) { float roundPx = bitmapAngle;//圆角的横向半径和纵向半径 Bitmap output = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); final int color = 0xff424242; final Paint paint = new Paint(); final Rect rect = new Rect(0, 0, source.getWidth(),source.getHeight()); final RectF rectF = new RectF(rect); paint.setAntiAlias(true); canvas.drawARGB(0, 0, 0, 0); paint.setColor(color); canvas.drawRoundRect(rectF, roundPx, roundPx, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(source, rect, rect, paint); source.recycle(); return output; } @Override public String key() { return "bitmapAngle()"; } } } 复制代码
好了,到了这里,关于图片框架的封装已经全部完成。而且该图片框架的封装已经成功应用到公司项目上,目前反馈良好。如有问题,欢迎交流指教!
如果有考虑引用该封装的话,可以采用下面的方式:
//根目录下build.gradle配置 allprojects { repositories { ... maven { url 'https://jitpack.io' } } } //项目build.gradle依赖 dependencies { compile 'com.github.mhlistener:ImageLoader:1.0.5' } //使用方式 1.Application中全局设置 ImageLoader.getInstance().setGlobalImageLoader(new PicassoLoader()); 2.界面中使用封装 ImageView imageView = findViewById(R.id.imageview); String url = "http://ww2.sinaimg.cn/large/7a8aed7bgw1eutsd0pgiwj20 go 0p0djn.jpg"; ImageLoader.getInstance() .load(url) .angle(80) .resize(400, 600) .centerCrop() .config(Bitmap.Config.RGB_565) .placeholder(R.mipmap.test) .error(R.mipmap.test) .skipLocalCache(true) .into(imageView); 复制代码
以上所述就是小编给大家介绍的《优雅地实现Android主流图片加载框架封装,可无侵入切换框架》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 无侵入埋点
- Decorators 低侵入性探索
- 如何低侵入的记录调用日志
- StateShot - 无侵入的历史状态管理库
- 从侵入式服务治理到 Service Mesh
- Matrix勒索病毒PRCP变种侵入政企单位
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
软件框架设计的艺术
[捷] Jaroslav Tulach / 王磊、朱兴 / 人民邮电出版社 / 2011-3 / 75.00元
本书帮助你解决API 设计方面的问题,共分3 个部分,分别指出学习API 设计是需要进行科学的训练的、Java 语言在设计方面的理论及设计和维护API 时的常见情况,并提供了各种技巧来解决相应的问题。 本书作者是NetBeans 的创始人,也是NetBeans 项目最初的架构师。相信在API 设计中遇到问题时,本书将不可或缺。 本书适用于软件设计人员阅读。一起来看看 《软件框架设计的艺术》 这本书的介绍吧!