优雅地实现Android主流图片加载框架封装,可无侵入切换框架
项目开发中,往往会随着需求的改变而切换到其它图片加载框架上去。如果最初代码设计的耦合度太高,那么恭喜你,成功入坑了。至今无法忘却整个项目一行行去复制粘贴被支配的恐惧。:)
那么是否存在一种方式 能够一劳永逸地解决这个痛点呢?下面我们来分析一下图片加载框架面对的现状和解决思路。
问题现状
一个优秀的框架一般在代码设计的时候已经封装很不错了,对于开发者而言框架的使用也是很方便,但是为什么说我们往往还要去做这方面的框架封装呢?原因很简单,实际项目开发中,我们不得不面对着日新月异的需求变化,想要在这个变化中最大程度的实现代码的可扩展性和变通性(当然还可以偷懒),不能因为牵一发而动全身,同时要将框架适配到实际项目,框架的再封装设计显得尤为重要。
不多废话,我们可以开始今天的图片封装之路了。
设计思路
图片框架的封装主要需要满足以下三点:
低耦合,方便将来的代码扩展。至少要支持目前市场上使用率最高的图片框架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度,即正常位置。此时不需要自己rotatepublic 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;}@Overridepublic void clearMemoryCache() {sLruCache.clear();}@Overridepublic void clearDiskCache() {File diskFile = new File(App.gApp.getCacheDir(), PICASSO_CACHE);if (diskFile.exists()) {//这边自行写删除代码
// FileUtil.deleteFile(diskFile);}}@Overridepublic 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;}@Overridepublic void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {if (this.callBack != null) {this.callBack.onBitmapLoaded(bitmap);}}@Overridepublic void onBitmapFailed(Exception e, Drawable errorDrawable) {if (this.callBack != null) {this.callBack.onBitmapFailed(e);}}@Overridepublic void onPrepareLoad(Drawable placeHolderDrawable) {}}class PicassoTransformation implements Transformation {private float bitmapAngle;protected PicassoTransformation(float corner){this.bitmapAngle = corner;}@Overridepublic 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;}@Overridepublic String key() {return "bitmapAngle()";}}}
好了,到了这里,关于图片框架的封装已经全部完成。而且该图片框架的封装已经成功应用到公司项目上,目前反馈良好。如有问题,欢迎交流指教!
如果感兴趣的话,欢迎在github给个star。代码已上传github链接
如果有考虑引用该封装的话,可以采用下面的方式:
//根目录下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/7a8aed7bgw1eutsd0pgiwj20go0p0djn.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);
转载于:https://blog.51cto.com/13983283/2311417
优雅地实现Android主流图片加载框架封装,可无侵入切换框架相关推荐
- Android常用图片加载库介绍及对比
前言 图片加载在 Android开发项目中是必不可少的,为了降低开发周期和难度,我们经常会选用一些图片加载的开源库,而Android发展到现在图片加载开源库也越来越多了,下面介绍几种开发中主流的图片加 ...
- android 加载进度,Android实现图片加载进度提示
本文实例为大家分享了Android实现图片加载进度提示的具体代码,供大家参考,具体内容如下 先上图: 实现原理: 第一个控件的实现原理是重写ImageView的onDraw()方法,利用Canvas的 ...
- Android Glide图片加载框架(四)回调与监听
文章目录 Android Glide图片加载框架系列文章 Android Glide图片加载框架(一)基本用法 Android Glide图片加载框架(二)源码解析之with() Android Gl ...
- Android Glide图片加载框架(三)缓存机制
文章目录 一.缓存简介 二.缓存用法 内存缓存方式 磁盘缓存方式 三.缓存KEY 四.内存缓存 内存缓存流程 五.磁盘缓存 磁盘缓存流程 Android Glide图片加载框架系列文章 Android ...
- Android Glide图片加载框架(二)源码解析之into()
文章目录 一.前言 二.源码解析 1.into(ImageView) 2.GlideContext.buildImageViewTarget() 3.RequestBuilder.into(Targe ...
- Android Glide图片加载框架(二)源码解析之load()
文章目录 一.前言 二.源码分析 1.load() Android Glide图片加载框架系列文章 Android Glide图片加载框架(一)基本用法 Android Glide图片加载框架(二)源 ...
- Android Glide图片加载框架(二)源码解析之with()
文章目录 一.前言 二.如何阅读源码 三.源码解析 1.with() Android Glide图片加载框架系列文章 Android Glide图片加载框架(一)基本用法 Android Glide图 ...
- Android Glide图片加载框架(一)基本用法
文章目录 一.前言 二.简介 三.基本用法 第一步:调用 Glide.with() 方法创建加载图片的实例 第二步:调用 load() 方法指定待加载的图片资源 第三步:调用 into() 方法绑定显 ...
- Android平滑图片加载和缓存库Glide使用详解
在图片加载库烂大街的今天,选择一个适合自己使用的图片加载库已经成为了每一个Android开发者的必经之路.现在市面上知名的图片加载库有UIL,Picasso,Volley ImageLoader,Fr ...
最新文章
- CowNew开源团队4月14日聚会
- Fiddler抓取数据并分析(完整的配置教程)
- java链表寻找中间节点
- 面试文员计算机水平考题,一般电脑文员面试上机操作的时候会考什么?
- Qt Post上传图片文件到服务器
- DCMTK:测试DcmSCP和DcmSCU类
- mysql definer_mysql常见问题之视图权限控制--安全性为DEFINER
- No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK? ide
- 用ghost备份和还原Linux系统
- 《DSP using MATLAB》示例Example4.6
- RecSys Challenge 历年推荐赛题汇总
- HBase 完全分布式的安装
- 今天是有纪念意义的一天--中国13亿人口日
- 微信小程序云开发教程-WXML入门-基本语法
- java web 基础知识 流程图
- 计算机东北大学与兰州大学哪个好,东北大学和兰州大学如何选择?上哪个比较好?...
- 如何用java股票量化交易接口读取股票数据?
- WPF——后台代码实现将多张图片拼接成一张
- 【过了】系统分析师考试心得
- python图像锐化,图像加强、锐化,利用 Python-OpenCV 来实现 4 种方法!
热门文章
- BurpSuite -Repeater
- Failed to load project configuration:xxx cannot read file .idea/misc.xml
- 中百信玄武库Kubernetes实践与探索
- 为什么华为a1路由器网速变慢_凭什么网速就比别人快?华为路由 A1 畅享版体验...
- 网上跳蚤市场网站系统HTML5+Vue+nodejs
- LaTeX入门(刘海洋)_百度云
- UReport2 多报表文件打印,ZIP下载实现
- PP实施经验分享(21)——(ECC版本)生产版本\BOM\工艺路线选择(涉及批量大小应用)
- Juniper路由器
- go get connectex: A connection attempt failed because the connected party did not properly respond