GitHub: 統一的圖片加載架構css
對於圖片加載框架,你們用到的多是Glide,Picasso或者Fresco,這基本上是主流的圖片加載框架,咱們使用它的時候,大都感受如臂使指,簡直愉快的不要不要的。可是咱們仍是發現至少有兩個問題,以Glide爲例,第一,當需求變更,你須要對圖片加載失敗時的情景添加一個單獨的佔位符,這個時候你就不得不在每個使用到Glide的地方去添加這樣的設置;第二,當你須要對項目進行重構時,或者目前的圖片加載框架沒法實現某些需求,而須要替換的時候,你可能仍是須要對原有項目大動干戈。git
你們回顧本身手頭上的代碼,不知道是否都面臨這樣的隱患?反正當我看到咱們團隊的項目代碼的時候,個人頭老是比平時大兩倍...你問我爲啥?一堆歷史遺留問題,好比最先就直接在項目中使用Glide,後來我建議說,至少稍微作點封裝,畢竟吃相不能太難看,因而才作了一層封裝,卻依然經不起新需求的考驗,更別提替換框架的程度了了(這可能就是爲何咱們團隊轉向了RN,由於誰都不想看過去的代碼了)。github
若是你覺得這是由於我是一個完美主義者,那麼可能沒有嘗試過一行一行粘貼複製,刪除重構的日子。設計模式
廢話講完,咱們正是開始吧api
咱們先聊聊封裝,封裝的好處你們都很熟悉,對外提供簡單接口屏蔽內部複雜,保護數據,保證安全....等,你們可能基本上都滾瓜爛熟了, 現在咱們在開發Android項目的時候封裝的主要目的卻再也不是這些了,爲何,由於咱們全部諸如okhttp,retrofit,Glide,等等框架自己就實現了完美的封裝,並達成了對外提供簡單接口屏蔽內部複雜,保護數據,保證安全等目的,若是僅僅是爲了這些目的,咱們大可沒必要在作封裝。緩存
那麼咱們封裝的新的使命是什麼呢,是爲了達成對模塊的控制,什麼意思呢?仍是以圖片加載框架爲例,假如你直接在業務代碼中使用了Glide,Picasso或者Fresco的話,也就意味着,你把圖片加載的控制權徹底交給了他們,後面你想對圖片加載流程作任何改動,你都須要一個一個去修改,那麼你就喪失了對圖片加載模塊的控制權。因此,我所說的對於模塊的控制,是你隨時可以以很小的代價修改甚至替換整個模塊。安全
這也是爲何如今各類發開框架已經把本身封裝的如此之好的狀況下,咱們依然須要對它作封裝的緣由。bash
好了,接下來,咱們就分析具體問題。架構
以Glide爲例,Glid經過鏈式調用,能夠隨意的調用各類圖片加載相關的設定,如緩存策略,動畫,佔位符等等,各種api數不勝數,而咱們如今先要把這些調用抽象成一個接口,進而就能輕鬆實現對它的封裝。框架
一個簡單的Glide的調用多是這樣的:
Glide.with(getContext())
.load(url)
.skipMemoryCache(true)
.placeholder(drawable)
.centerCrop()
.animate(animator)
.into(img);複製代碼
儘管沒有使用Glide全部的圖片加載相關的設置,可是你們應該能感覺到,它的圖片加載設置選項十分豐富,也很隨意,那麼咱們究竟應該如何把它封裝到一個接口裏面去呢?可能你首先想到是這種:
public interface ImageLoader{
static void showImage(ImageView v, Context context,String url, boolean skipMemoryCache,int placeholder,ViewPropertyAnimation.Animator animator)
}複製代碼
這顯然是頗有問題的,對於一個有不少可選項的接口作封裝,既要保留豐富的可選項,還要保證統一而簡介的調用。這麼一長串參數顯然有傷大雅。
那麼應該如何設計呢?咱們能夠從這個角度來分析,對於圖片加載而言,什麼是最基本最重要的必選項,什麼是無關緊要的可選項:
那麼咱們的接口初具雛形了
public interface ImageLoader{
void showImage(ImageView imageview, String url, Context context,ImageLoaderOptions options);
void showImage(ImageView imageview,int drawable,Context context,ImageLoaderOptions options);
}複製代碼
這樣是否是就行了呢?也不是,咱們還能夠在繼續探索,
咱們發現ImageView內部其實包含了Context這個參數,徹底能夠省略,因此咱們的基本參數應該是:url,ImageView,options,
public interface ImageLoader{
void showImage(ImageView imageview, String url, ImageLoaderOptions options);
void showImage(ImageView imageview, int drawable,ImageLoaderOptions options);
}複製代碼
而後咱們再來看看方法中定義的ImageLoaderOptions,這個其實比較簡單,基本上Glide有多少可選項,你就能夠往裏面加多少屬性。因爲這些屬性都是可選擇的,所以咱們須要使用Builder模式來構建它,具體就不贅述了。
那麼,到這裏,咱們對於Glide的封裝的設計就基本完成了。
咱們說了想要打造一個統一的圖片加載框架,也就是說,無論Glide,仍是Fresco,或者Picasso都能在這套架構下愉快的玩耍。其實咱們只要在封裝Glide的基礎上進一步的作出改進便可,由於當咱們封裝Glide的時候,就已是對圖片加載的抽象了。
咱們首先來看,以前抽象的接口整體上在其餘的圖片加載框架中都是可用的,不過因爲Fresco的特殊設計,本身實現了圖片容器,致使了一點問題,可是這也很簡單,咱們在接口裏面用View做爲圖片容器便可。
public interface ImageLoader{
void showImage(View v, String url, ImageLoaderOptions options);
void showImage(View v, int drawable,ImageLoaderOptions options);
}複製代碼
好了,上面這個接口基本上能夠完美兼容Glide,Picasso,Fresco這三種加載庫,如今的問題是如何實現他們的可替換。這個時候咱們就須要一種設計模式(策略模式火燒眉毛的跳出來講,選我選我!)
沒錯,就是策略模式,它的設計圖以下:
至此,咱們在設計上已經完成了一個統一的圖片加載架構的設計,可是有一個問題我特地留到了最後,就是ImageLoaderOptions的內部的構造。
當咱們只須要封裝一個Glide的時候,ImageLoaderOptions能夠和Glide中的那些設置項徹底匹配,只要你願意,你能夠把Glide裏面的全部圖片加載的相關的設置項都放進去。可是,若是咱們要兼容三個加載框架甚至更多的時候,還能這樣作麼?
理論上是能夠的,不過當你這麼幹了,那麼ImageLoaderOptions內部多是多是這樣的:
public class ImageLoaderOptions {
//Glide的設置項
private int placeHolder=-1; //當沒有成功加載的時候顯示的圖片
private ImageReSize size=null; //從新設定容器寬高
private int errorDrawable=-1; //加載錯誤的時候顯示的drawable
private boolean isCrossFade=false; //是否漸變平滑的顯示圖片
private boolean isSkipMemoryCache = false; //是否跳過內存緩存
private ViewPropertyAnimation.Animator animator = null; // 圖片加載動畫
...
...
//Fresco的設置項
private int placeHolder=-1; //當沒有成功加載的時候顯示的圖片
private Drawable pressedStateOverlay =null; //按下時顯示的圖層
private boolean isCrossFade=false; //是否漸變平滑的顯示圖片
...
...
}複製代碼
你們很容易發現,其實各個圖片加載框架之間的設置項不少功能都是重疊的,好比佔位符,漸進加載,緩存等等,也有一些設置項是相似的,所以實際上咱們應該把他們合併在一塊兒,也就是說,當咱們思考對於ImageLoaderOptions的設計的時候,咱們應該首先把幾個框架共同和類似的設置項合併,由於這表明着圖片加載領域最廣泛最重要的需求。其次咱們再按需加入本身須要的各個框架之間有差別的設置項。
下面是我對於這個統一圖片加載架構的具體實現,你們能夠僅做參考。
接口定義
public interface ImageLoaderStrategy{
void showImage(View v, String url, ImageLoaderOptions options);
void showImage(View v, int drawable,ImageLoaderOptions options);
}複製代碼
設置項定義
public class ImageLoaderOptions {
//你能夠把三個圖片加載框架全部的共同或類似設置項搬過來,如今僅僅用如下幾種做爲範例演示。
private int placeHolder=-1; //當沒有成功加載的時候顯示的圖片
private ImageReSize size=null; //從新設定容器寬高
private int errorDrawable=-1; //加載錯誤的時候顯示的drawable
private boolean isCrossFade=false; //是否漸變平滑的顯示圖片
private boolean isSkipMemoryCache = false; //是否跳過內存緩存
private ViewPropertyAnimation.Animator animator = null; // 圖片加載動畫
private ImageLoaderOptions(ImageReSize resize, int placeHolder, int errorDrawable, boolean isCrossFade, boolean isSkipMemoryCache, ViewPropertyAnimation.Animator animator){
this.placeHolder=placeHolder;
this.size=resize;
this.errorDrawable=errorDrawable;
this.isCrossFade=isCrossFade;
this.isSkipMemoryCache=isSkipMemoryCache;
this.animator=animator;
}
public class ImageReSize{
int reWidth=0;
int reHeight=0;
public ImageReSize(int reWidth,int reHeight){
if (reHeight<=0){
reHeight=0;
}
if (reWidth<=0) {
reWidth=0;
}
this.reHeight=reHeight;
this.reWidth=reWidth;
}
}
public static final class Builder {
private int placeHolder=-1;
private ImageReSize size=null;
private int errorDrawable=-1;
private boolean isCrossFade =false;
private boolean isSkipMemoryCache = false;
private ViewPropertyAnimation.Animator animator = null;
public Builder (){
}
public Builder placeHolder(int drawable){
this.placeHolder=drawable;
return this;
}
public Builder reSize(ImageReSize size){
this.size=size;
return this;
}
public Builder anmiator(ViewPropertyAnimation.Animator animator){
this.animator=animator;
return this;
}
public Builder errorDrawable(int errorDrawable){
this.errorDrawable=errorDrawable;
return this;
}
public Builder isCrossFade(boolean isCrossFade){
this.isCrossFade=isCrossFade;
return this;
}
public Builder isSkipMemoryCache(boolean isSkipMemoryCache){
this.isSkipMemoryCache=isSkipMemoryCache;
return this;
}
public ImageLoaderOptions build(){
return new ImageLoaderOptions(this.size,this.placeHolder,this.errorDrawable,this.isCrossFade,this.isSkipMemoryCache,this.animator);
}
}複製代碼
下面以Glide實現該接口的方式:
public class GlideImageLoaderStrategy implements ImageLoaderStrategy {
@Override
public void showImage(View v, String url, ImageLoaderOptions options) {
if (v instanceof ImageView) {
//將類型轉換爲ImageView
ImageView imageView= (ImageView) v;
//裝配基本的參數
DrawableTypeRequest dtr = Glide.with(imageView.getContext()).load(url);
//裝配附加參數
loadOptions(dtr, options).into(imageView);
}
}
@Override
public void showImage(View v, int drawable, ImageLoaderOptions options) {
if (v instanceof ImageView) {
ImageView imageView= (ImageView) v;
DrawableTypeRequest dtr = Glide.with(imageView.getContext()).load(drawable);
loadOptions(dtr, options).into(imageView);
}
}
//這個方法用來裝載由外部設置的參數
private DrawableTypeRequest loadOptions(DrawableTypeRequest dtr,ImageLoaderOptions options){
if (options==null) {
return dtr;
}
if (options.getPlaceHolder()!=-1) {
dtr.placeholder(options.getPlaceHolder());
}
if (options.getErrorDrawable()!=-1){
dtr.error(options.getErrorDrawable());
}
if (options.isCrossFade()) {
dtr.crossFade();
}
if (options.isSkipMemoryCache()){
dtr.skipMemoryCache(options.isSkipMemoryCache());
}
if (options.getAnimator()!=null) {
dtr.animate(options.getAnimator());
}
if (options.getSize()!=null) {
dtr.override(options.getSize().reWidth,options.getSize().reHeight);
}
return dtr;
}
}複製代碼
Picsso,Fresco的接口實現類依照Glide。
下面就是最後一步,實現整個圖片加載架構的管理類,用於對外提供圖片加載服務和圖片加載框架的替換
public class ImageLoaderStrategyManager implements ImageLoaderStrategy {
private static final ImageLoaderStrategyManager INSTANCE = new ImageLoaderStrategyManager();
private ImageLoaderStrategy imageLoader;
private ImageLoaderStrategyManager(){
//默認使用Glide
imageLoader=new GlideImageLoaderStrategy();
}
public static ImageLoaderStrategyManager getInstance(){
return INSTANCE;
}
//可實時替換圖片加載框架
public void setImageLoader(ImageLoaderStrategy loader) {
if (loader != null) {
imageLoader=loader;
}
}
@Override
public void showImage(@NonNull View mView, @NonNull String mUrl, @Nullable ImageLoaderOptions options) {
imageLoader.showImage(mView,mUrl,options);
}
@Override
public void showImage(@NonNull View mView, @NonNull int mDraeable, @Nullable ImageLoaderOptions options) {
imageLoader.showImage(mView,mDraeable,options);
}
}複製代碼
至此,整個圖片加載架構都已經設計完畢了,咱們也能夠基本實現了對圖片加載模塊的控制。
這個小的圖片加載架構是否是已經很完美了呢?其實也不是,因爲Fresco的特殊,當咱們切換到Fresco,或者從Fresco切換到其餘加載框架的時候,咱們可能仍然須要處處去修改xml文件的圖片容器節點(ImageView/DraweeView),由於Fresco使用的時自家的組件。不過我也考慮過一種解決方案,那就是把圖片容器(ImageView/DraweeView)節點放在一個單獨的xml文件中,使用merge的方式添加到佈局文件中,並在代碼層面使統一用View 來獲取圖片容器(ImageView/DraweeView)的實例作相應操做。
項目已經上傳了github,點此獲取,求star !
假如你實在理解不了控制力這個概念,也能夠理解爲打造高內聚低耦合的模塊