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

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

引言

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

接下來(lái)舞肆,我將介紹另一種更為巧妙的方法來(lái)實(shí)現(xiàn)焦辅,以期大大提高其擴(kuò)展性。我們將以 item 的布局 id 作為區(qū)別item的唯一標(biāo)志并結(jié)合兩種設(shè)計(jì)模式,為大家呈現(xiàn)一種新穎椿胯,簡(jiǎn)潔的方式來(lái)實(shí)現(xiàn)此類復(fù)雜布局筷登。其中如有紕漏,望請(qǐng)悉心指出压状。

必備知識(shí)

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

訪問(wèn)者模式

  1. 概念
    Java 中的訪問(wèn)者模式屬于一種行為型設(shè)計(jì)模式种冬,核心主要由訪問(wèn)者與被訪問(wèn)者兩部分組成镣丑。一般對(duì)于同一場(chǎng)景來(lái)說(shuō),被訪問(wèn)者都是由不同類的類型所表示娱两,而不同的訪問(wèn)者可以對(duì)被訪問(wèn)者進(jìn)行不同的訪問(wèn)操作莺匠。其中,被訪問(wèn)者常利用集合結(jié)構(gòu)來(lái)存儲(chǔ)(比如 List )十兢,訪問(wèn)者通過(guò)遍歷集合實(shí)現(xiàn)對(duì)其中存儲(chǔ)的元素的逐個(gè)操作趣竣。

  2. UML類圖

訪問(wèn)者模式UML類圖
訪問(wèn)者模式UML類圖

出處:維基百科

UML解讀

UML類圖中有兩個(gè)類:訪問(wèn)者(Visitor)和被訪問(wèn)者(Element),然后有多個(gè)具體訪問(wèn)者繼承訪問(wèn)者 Visitor(eg: ConcreateVisitor1 )摇庙,也有多個(gè)具體被訪問(wèn)者繼承被訪問(wèn)者 Element(eg: ConcreateElementA ) 。首先遥缕,Visitor 中為每個(gè)具體被訪問(wèn)者定義了一個(gè)可訪問(wèn)具體被訪問(wèn)者操作的方法(通過(guò)注入具體被訪問(wèn)者的引用)卫袒;其次,Element 中定義了一個(gè)接受訪問(wèn)的方法 accept单匣,并且依賴注入訪問(wèn)者 visitor夕凝,以便訪問(wèn)者可以訪問(wèn)被訪問(wèn)者;然后户秤,Object Structure 對(duì)象結(jié)構(gòu)主要用于存儲(chǔ)被訪問(wèn)者码秉。因此,對(duì)于每個(gè)被訪問(wèn)者都應(yīng)先從該對(duì)象結(jié)構(gòu)中取出來(lái)鸡号。最后转砖,客戶端 Client 定義集合對(duì)象收集被訪問(wèn)者數(shù)據(jù),通過(guò)對(duì)集合的遍歷完成訪問(wèn)者對(duì)每一個(gè)被訪問(wèn)元素的訪問(wèn)操作鲸伴。

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

工廠方法模式

  1. 概念

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

  2. UML類圖

    工廠模式
    工廠模式

出處:維基百科

UML解讀

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

具體例子詳見(jiàn)推薦博客工廠方法模式

BaseViewHolder的封裝

/**
 * 封裝的 viewholder 用于獲取各個(gè) item 上的控件 采用集合存儲(chǔ)取過(guò)的控件
 */

public  class BaseViewHolder extends RecyclerView.ViewHolder {
protected View itemView;//每個(gè)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 來(lái)使用岖食,因此你無(wú)需在每個(gè)適配器里面再定義內(nèi)部類 MyViewHolderBaseViewHolder 可以在構(gòu)造器中獲取到每個(gè) itemitemView舞吭,然后就可以定義一個(gè)快速獲取 itemView 上各個(gè)子控件的方法 getView,以后在適配器中獲取 item 子控件能夠隨時(shí)調(diào)用此方法.

BetterViewHolder

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

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

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

實(shí)例解析

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

布局代碼見(jiàn)底部源碼

  • 定義訪問(wèn)者 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);
    }
  • 定義被訪問(wèn)者 Visitable
    /*
    *定義抽象的被訪問(wèn)者 type方法捐腿,用來(lái)接收/引用一個(gè)抽象訪問(wèn)者對(duì)象,以便利用這個(gè)對(duì)象進(jìn)行操作;
    */
    public abstract class Visitable {
    public abstract int type(TypeFactory factory);
    }  
  • 定義四個(gè)實(shí)體類并繼承Visitable

      /**
       * Created by hrx on 2017/4/30.
       * 具體的被訪問(wèn)者
       * 實(shí)現(xiàn)type抽象方法纵朋,通過(guò)傳入的具體訪問(wèn)者參數(shù)、
       * 調(diào)用具體訪問(wèn)者對(duì)該對(duì)象的訪問(wèn)操作方法實(shí)現(xiàn)訪問(wèn)邏輯;
       * 比如這就是利用抽象訪問(wèn)者 TypeFactory 引用來(lái)調(diào)用操作方法獲取對(duì)應(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);
         }
       }  
    
  • 具體訪問(wèn)者 TypeFactoryList

      /*
      * 具體的訪問(wèn)者實(shí)現(xiàn)了抽象訪問(wèn)者的方法
      * 同時(shí) onCreateViewHolder 也是利用工廠方法模式創(chuàng)建了各個(gè) item 的 viewholder 實(shí)例
      */
    
      public class TypeFactoryList extends TypeFactory {
      //聲明每個(gè)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)建細(xì)節(jié)已經(jīng)抽象到 TypeFactoryList 中去實(shí)現(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);
      }
    
      //此處的實(shí)現(xiàn)交由 BetterViewHolder 的各個(gè)子類去實(shí)現(xiàn)茄袖,故此處 Java 會(huì)根據(jù)相應(yīng)的子類去獲取其下實(shí)現(xiàn)的 bindDataToItem(),利用JAVA動(dòng)態(tài)分派而無(wú)需進(jìn)行類型檢查
      @Override
      public void onBindViewHolder(BetterViewHolder holder, int position) {
          holder.bindDataToItem(mVisitables.get(position),position);
      }
    
      //此處即代表訪問(wèn)者模式中的客戶端調(diào)用被訪問(wèn)者的 type()操软,進(jìn)行訪問(wèn)操作獲取其布局 id
      @Override
      public int getItemViewType(int position) {
          return mVisitables.get(position).type(factory);
      }
      @Override
      public int getItemCount() {
          return mVisitables.size();
      }
      }  
    

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

  • 四個(gè) BetterViewHolder 的子類

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

代碼見(jiàn)底部源碼

  • 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);
        //默認(rèn)4列
        final GridLayoutManager manager = new GridLayoutManager(this, 4);
        //此方法定義每個(gè)item占幾列,有點(diǎn)類似線性布局的權(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<>();
        //按布局的順序依次加入各個(gè)被訪問(wèn)者
        Banner banner = new Banner();
        mVisitable.add(banner);
        Category category = new Category();
        mVisitable.add(category);
        //加入4個(gè)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 集合中被訪問(wèn)者的加入順序即代表了最終顯示出來(lái)的順序肘交,并且集合中的每個(gè)元素僅能代表其中一個(gè) item,意味著如果你要重復(fù)該 item 就要重復(fù)聲明一個(gè)實(shí)體再加入集合中扑馁。

總結(jié)

利用訪問(wèn)者模式和工廠方法模式大大解耦了上述復(fù)雜布局的實(shí)現(xiàn)過(guò)程涯呻,同時(shí)可擴(kuò)展性大大提高。如果往后還需修改布局腻要,只需修改對(duì)應(yīng) item 的布局文件和數(shù)據(jù)的綁定复罐。而若是增加 item,那么只需定義新的實(shí)體加入被訪問(wèn)者集合中雄家,同時(shí)編寫布局文件及對(duì)應(yīng)的 ViewHolder 實(shí)現(xiàn)即可效诅。這樣一來(lái),不同 item 間就不會(huì)相互影響趟济,變得易維護(hù)和易擴(kuò)展乱投,相信你學(xué)會(huì)之后,一定會(huì)愛(ài)上此法顷编。

最后戚炫,謝謝你看到這里,歡迎交流意見(jiàn)媳纬。

源碼地址

感謝

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市钮惠,隨后出現(xiàn)的幾起案子茅糜,更是在濱河造成了極大的恐慌,老刑警劉巖萌腿,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件限匣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)米死,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門锌历,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人峦筒,你說(shuō)我怎么就攤上這事究西。” “怎么了物喷?”我有些...
    開(kāi)封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵卤材,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我峦失,道長(zhǎng)扇丛,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任尉辑,我火速辦了婚禮帆精,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘隧魄。我一直安慰自己卓练,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布购啄。 她就那樣靜靜地躺著襟企,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狮含。 梳的紋絲不亂的頭發(fā)上顽悼,一...
    開(kāi)封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音辉川,去河邊找鬼表蝙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛乓旗,可吹牛的內(nèi)容都是我干的府蛇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼屿愚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼汇跨!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起妆距,我...
    開(kāi)封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤穷遂,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后娱据,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蚪黑,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了忌穿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抒寂。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖掠剑,靈堂內(nèi)的尸體忽然破棺而出屈芜,到底是詐尸還是另有隱情,我是刑警寧澤朴译,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布井佑,位于F島的核電站,受9級(jí)特大地震影響眠寿,放射性物質(zhì)發(fā)生泄漏躬翁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一盯拱、第九天 我趴在偏房一處隱蔽的房頂上張望姆另。 院中可真熱鬧,春花似錦坟乾、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至间学,卻和暖如春殷费,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背低葫。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工详羡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嘿悬。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓实柠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親善涨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子窒盐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

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

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