巧用設(shè)計模式實現(xiàn)Recyclerview各種復(fù)雜Item類型

版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載

引言

在實際項目的開發(fā)中,首頁的布局基本上都是復(fù)雜的 UI,而我們的實現(xiàn)思路一般就是利用 RecyclerView 結(jié)合 getItemType(),并在適配器里根據(jù)不同的 item 類型去創(chuàng)建不同的 ViewHolder,最后在 onBindViewHolder() 中依然是根據(jù) item 類型來綁定對應(yīng)的數(shù)據(jù)。這種方法是最基本的方法,相信大家都懂。但是,其缺點也很明顯,就是可擴展性太差。

接下來,我將介紹另一種更為巧妙的方法來實現(xiàn),以期大大提高其擴展性。我們將以 item 的布局 id 作為區(qū)別item的唯一標志并結(jié)合兩種設(shè)計模式,為大家呈現(xiàn)一種新穎,簡潔的方式來實現(xiàn)此類復(fù)雜布局。其中如有紕漏,望請悉心指出。

必備知識

本實現(xiàn)方法主要用到了 Java 設(shè)計模式中的訪問者模式和工廠方法模式,亦涉及到 ViewHolder 的封裝技巧。

訪問者模式

  1. 概念
    Java 中的訪問者模式屬于一種行為型設(shè)計模式,核心主要由訪問者與被訪問者兩部分組成。一般對于同一場景來說,被訪問者都是由不同類的類型所表示,而不同的訪問者可以對被訪問者進行不同的訪問操作。其中,被訪問者常利用集合結(jié)構(gòu)來存儲(比如 List ),訪問者通過遍歷集合實現(xiàn)對其中存儲的元素的逐個操作。

  2. UML類圖

訪問者模式UML類圖
訪問者模式UML類圖

出處:維基百科

UML解讀

UML類圖中有兩個類:訪問者(Visitor)和被訪問者(Element),然后有多個具體訪問者繼承訪問者 Visitor(eg: ConcreateVisitor1 ),也有多個具體被訪問者繼承被訪問者 Element(eg: ConcreateElementA ) 。首先,Visitor 中為每個具體被訪問者定義了一個可訪問具體被訪問者操作的方法(通過注入具體被訪問者的引用);其次,Element 中定義了一個接受訪問的方法 accept,并且依賴注入訪問者 visitor,以便訪問者可以訪問被訪問者;然后,Object Structure 對象結(jié)構(gòu)主要用于存儲被訪問者。因此,對于每個被訪問者都應(yīng)先從該對象結(jié)構(gòu)中取出來。最后,客戶端 Client 定義集合對象收集被訪問者數(shù)據(jù),通過對集合的遍歷完成訪問者對每一個被訪問元素的訪問操作。

具體例子詳見推薦博客 Java設(shè)計模式之 訪問者模式【Visitor Pattern】

工廠方法模式

  1. 概念

    工廠方法模式是一種實現(xiàn)了“工廠”概念的面向?qū)ο笤O(shè)計模式 ,是處理在不指定對象具體類型的情況下創(chuàng)建對象的問題。工廠方法模式的實質(zhì)是“定義一個創(chuàng)建對象的接口,但讓實現(xiàn)這個接口的類來決定實例化哪個類。工廠方法讓類的實例化推遲到子類中進行。”

  2. UML類圖

    工廠模式
    工廠模式

出處:維基百科

UML解讀

首先在創(chuàng)建器 Creator 中定義一個工廠方法用于生產(chǎn)未指定具體類型的產(chǎn)品,其次,子類--具體創(chuàng)建器 ConcreteCreator 實現(xiàn)父類工廠方法,給出創(chuàng)建具體產(chǎn)品類型的實現(xiàn),最后,客戶端只需調(diào)用具體創(chuàng)建者中的方法即可得到所需的產(chǎn)品。

具體例子詳見推薦博客工廠方法模式

BaseViewHolder的封裝

/**
 * 封裝的 viewholder 用于獲取各個 item 上的控件 采用集合存儲取過的控件
 */

public  class BaseViewHolder extends RecyclerView.ViewHolder {
protected View itemView;//每個item的布局視圖view
protected SparseArray<View> list;//itemView上所有控件的集合

public BaseViewHolder(View itemView) {
    super(itemView);
    this.itemView = itemView;
    list=new SparseArray<>();
}
/**
*獲取 itemView 上控件
*/
 public <T> T getView(int id) {
    View view = list.get(id);
    if (view == null) {
        view = itemView.findViewById(id);
        list.put(id, view);
    }
    return (T) view;
}

BaseViewHolder 繼承至 RecyclerView.ViewHolder ,可作為 Recyclerview 適配器中通用的 ViewHolder 來使用,因此你無需在每個適配器里面再定義內(nèi)部類 MyViewHolderBaseViewHolder 可以在構(gòu)造器中獲取到每個 itemitemView,然后就可以定義一個快速獲取 itemView 上各個子控件的方法 getView,以后在適配器中獲取 item 子控件能夠隨時調(diào)用此方法.

BetterViewHolder

public abstract class BetterViewHolder<T> extends BaseViewHolder {
public BetterViewHolder(View itemView) {
    super(itemView);
}

/**
 * 綁定 item 的數(shù)據(jù)
 * @param t 每個item的實體引用
 */
public abstract void bindDataToItem(T t,int position);
}  

BetterViewHolder 聲明為抽象類型,在 BaseViewHolder 基礎(chǔ)上再次封裝了一層。主要定義了一個抽象方法用于實現(xiàn)適配器中 onBindViewHolder(T t,int position) 的功能。但由于子類的實體類型不可確定,故需要借助泛型技巧,定義T來表示子類的泛型。因此,子類只需繼承該類,并指定子類所需的實體類型即可。

實例解析

  • 本例中定義了四種 item 布局類型,如下圖所示

布局代碼見底部源碼

  • 定義訪問者 TypeFactory
    public abstract class TypeFactory {  

    public abstract int type(Banner banner);

    public abstract int type(Category category);

    public abstract int type(Item item);

    public abstract int type(Footer footer);
    //工廠方法模式應(yīng)用 
    public  abstract BetterViewHolder onCreateViewHolder(View itemView,int  type);
    }
  • 定義被訪問者 Visitable
    /*
    *定義抽象的被訪問者 type方法,用來接收/引用一個抽象訪問者對象,以便利用這個對象進行操作;
    */
    public abstract class Visitable {
    public abstract int type(TypeFactory factory);
    }  
  • 定義四個實體類并繼承Visitable

      /**
       * Created by hrx on 2017/4/30.
       * 具體的被訪問者
       * 實現(xiàn)type抽象方法,通過傳入的具體訪問者參數(shù)、
       * 調(diào)用具體訪問者對該對象的訪問操作方法實現(xiàn)訪問邏輯;
       * 比如這就是利用抽象訪問者 TypeFactory 引用來調(diào)用操作方法獲取對應(yīng)該 Banner 關(guān)聯(lián)的布局  id
       */
    
      public class Banner extends Visitable{
        @Override
        public int type(TypeFactory factory) {
           return factory.type(this);
        }
       }  
    
      public class Category extends Visitable {
        @Override
        public int type(TypeFactory factory) {
           return factory.type(this);
        }
      }  
      public class Item extends Visitable {
          private int position;
          @Override
          public int type(TypeFactory factory) {
              return factory.type(this);
          }
      
          public int getPosition() {
              return position;
          }
      
          public void setPosition(int position) {
              this.position = position;
          }
      }
      public class Footer extends Visitable {
        @Override
         public int type(TypeFactory factory) {
            return factory.type(this);
         }
       }  
    
  • 具體訪問者 TypeFactoryList

      /*
      * 具體的訪問者實現(xiàn)了抽象訪問者的方法
      * 同時 onCreateViewHolder 也是利用工廠方法模式創(chuàng)建了各個 item 的 viewholder 實例
      */
    
      public class TypeFactoryList extends TypeFactory {
      //聲明每個item的布局id
      public static final int BANNER = R.layout.banner;
      public static final int CATEGORY = R.layout.category;
      public static final int ITEM = R.layout.item;
      public static final int FOOTER = R.layout.footer;
    
      @Override
      public int type(Banner banner) {
          return BANNER;
      }
    
      @Override
      public int type(Category category) {
          return CATEGORY;
      }
    
      @Override
      public int type(Item item) {
          return ITEM;
      }
    
      @Override
      public int type(Footer footer) {
          return FOOTER;
      }
    
      @Override
      public BetterViewHolder onCreateViewHolder(View itemView, int type) {
          BetterViewHolder viewHolder = null;
          switch (type) {
              case BANNER:
                  viewHolder = new BannerViewHolder(itemView);
                  break;
              case CATEGORY:
                  viewHolder = new CategoryViewHolder(itemView);
                  break;
              case ITEM:
                  viewHolder = new ItemViewHolder(itemView);
                  break;
              case FOOTER:
                  viewHolder = new FooterViewHolder(itemView);
                  break;
              default:
                  break;
          }
          return viewHolder;
       }
      }  
    
  • 定義適配器

      public class MainActivityAdapter extends RecyclerView.Adapter<BetterViewHolder> {
      private List<Visitable> mVisitables;
      private TypeFactory factory;
    
      public MainActivityAdapter(List<Visitable> mVisitables) {
          this.mVisitables = mVisitables;
          factory = new TypeFactoryList();
      }
      //此 ViewHolder 的創(chuàng)建細節(jié)已經(jīng)抽象到 TypeFactoryList 中去實現(xiàn)了 此處等同與獲取工廠生產(chǎn)的產(chǎn)品
      @Override
      public BetterViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
          View itemView = View.inflate(parent.getContext(), viewType, null);
          return factory.onCreateViewHolder(itemView, viewType);
      }
    
      //此處的實現(xiàn)交由 BetterViewHolder 的各個子類去實現(xiàn),故此處 Java 會根據(jù)相應(yīng)的子類去獲取其下實現(xiàn)的 bindDataToItem(),利用JAVA動態(tài)分派而無需進行類型檢查
      @Override
      public void onBindViewHolder(BetterViewHolder holder, int position) {
          holder.bindDataToItem(mVisitables.get(position),position);
      }
    
      //此處即代表訪問者模式中的客戶端調(diào)用被訪問者的 type(),進行訪問操作獲取其布局 id
      @Override
      public int getItemViewType(int position) {
          return mVisitables.get(position).type(factory);
      }
      @Override
      public int getItemCount() {
          return mVisitables.size();
      }
      }  
    

該適配器的數(shù)據(jù)就是得到訪問者模式中的對象結(jié)構(gòu) Object Structure 中存儲的被訪問者集合,對應(yīng)到例子中就是 mVisitables 集合。同時需要一個訪問者引用,以便該適配器(客戶端)能夠利用這個引用獲取每個被訪問者關(guān)聯(lián)到的布局Id(利用該引用執(zhí)行某些操作),如mVisitables.ge(position).type(factory)可以獲取到每個被訪問者關(guān)聯(lián)到的布局 id

  • 四個 BetterViewHolder 的子類

    四個子類代表了其對應(yīng) itemViewHolder ,可以在該類上實現(xiàn) item 上的操作,比如點擊事件,設(shè)置 item 上子控件的所有數(shù)據(jù)等。

代碼見底部源碼

  • Activity 收集數(shù)據(jù)并設(shè)置適配器
    public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private List<Visitable> mVisitable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        //默認4列
        final GridLayoutManager manager = new GridLayoutManager(this, 4);
        //此方法定義每個item占幾列,有點類似線性布局的權(quán)重屬性
        manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                if (position > 2 && position < 7) {
                    return 1;
                }
                return 4;
            }
        });
        recyclerView.setLayoutManager(manager);
        initData();
        recyclerView.setAdapter(new MainActivityAdapter(mVisitable));

    }

    private void initData() {
        mVisitable = new ArrayList<>();
        //按布局的順序依次加入各個被訪問者
        Banner banner = new Banner();
        mVisitable.add(banner);
        Category category = new Category();
        mVisitable.add(category);
        //加入4個item
        for (int i = 0; i < 5; i++) {
            Item item = new Item();
            item.setPosition(i + 2);
            mVisitable.add(item);
        }
        Footer footer = new Footer();
        mVisitable.add(footer);
    }
}

注:mVisitable 集合中被訪問者的加入順序即代表了最終顯示出來的順序,并且集合中的每個元素僅能代表其中一個 item,意味著如果你要重復(fù)該 item 就要重復(fù)聲明一個實體再加入集合中。

總結(jié)

利用訪問者模式和工廠方法模式大大解耦了上述復(fù)雜布局的實現(xiàn)過程,同時可擴展性大大提高。如果往后還需修改布局,只需修改對應(yīng) item 的布局文件和數(shù)據(jù)的綁定。而若是增加 item,那么只需定義新的實體加入被訪問者集合中,同時編寫布局文件及對應(yīng)的 ViewHolder 實現(xiàn)即可。這樣一來,不同 item 間就不會相互影響,變得易維護和易擴展,相信你學(xué)會之后,一定會愛上此法。

最后,謝謝你看到這里,歡迎交流意見。

源碼地址

感謝

[譯]關(guān)于 Android Adapter,你的實現(xiàn)方式可能一直都有問題
Java設(shè)計模式之 訪問者模式【Visitor Pattern】
Java設(shè)計模式之 工廠方法模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,836評論 18 139
  • 寫在前面:參考YoKey,感謝。附上他的鏈接:http://www.lxweimin.com/p/d30fd8da4...
    喜歡丶下雨天閱讀 3,272評論 0 9
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,738評論 18 399
  • 對象的創(chuàng)建與銷毀 Item 1: 使用static工廠方法,而不是構(gòu)造函數(shù)創(chuàng)建對象:僅僅是創(chuàng)建對象的方法,并非Fa...
    孫小磊閱讀 2,019評論 0 3
  • 將所有的月光聚集 濃縮成一顆明亮的心 在你的窗前 靜靜的看著你,看著你 將所有的河流流淌成澎湃的心潮 那是我愛你的...
    曉宇啟航閱讀 431評論 2 5