Android複雜資料流的“高效”渲染(二)

wutongke發表於2017-03-03

上篇Android ListView中複雜資料流的高效渲染文章中介紹如何高效利用ListView的快取進行渲染。之後有挺多同學有些疑惑,希望可以有一個demo,於是利用業餘時間把demo櫓出來了,如果有什麼問題大家可以評論或者在ComplexDataStream issue中提issue。這裡貼一下demo的地址:ComplexDataStream。另個人覺得這個思路實際增加了程式碼複雜度,在邏輯上並不高效,於是在題目中加了引號,但是實測一個複雜列表中可以節約10-20m的記憶體,這一點是很有誘惑力的。下面結合程式碼介紹一下。

程式碼結構

Android複雜資料流的“高效”渲染(二)
Paste_Image.png

  • Model
    model中是所有的原始資料類,這裡為了方便,每種資料的名字實際包含了需要展示的模型:如CardWithTitleItem資料實際需要展示一個標題和一個卡片,HeaderImageCardItem需要展示一個頭部、圖片、卡片。

Android複雜資料流的“高效”渲染(二)
Paste_Image.png

Android複雜資料流的“高效”渲染(二)
Paste_Image.png

  • Adapter
    Adapter中放置了adapter和各種型別的holder,這裡把多種資料型別拆分成了card、divider、header、image、link、text、title,併為每種型別設定了相應的佈局。

  • Util
    Util中設定如何transform資料到相應的展示模版,以及解決按壓效果的問題。

程式碼思路

我們的目的是將複雜的資料型別進行拆分,從而達到細顆粒的view複用,降低記憶體佔用。

  1. 確定拆分後的展示型別,這裡使用了一個enum型別:
    public enum ItemType {
     TITLE,
     CARD,
     HEADER,
     IMAGE,
     TEXT,
     LINK,
     DIVIDER
    }複製程式碼
  2. transform資料到模板,拆分後一個資料型別對應多個模板,這裡我們使用hashmap建立資料到模板的影射關係:
    private static Map<Class, List<ItemType>> map = new HashMap<>();複製程式碼
     public static List<ItemWrap> getTransformedItem(List<BaseItem> baseItems) {
         List<ItemWrap> itemWraps = new ArrayList<>();
         for (BaseItem baseItem : baseItems) {
             for (ItemType itemType : map.get(baseItem.getClass())) {
                 ItemWrap temp = new ItemWrap(baseItem, itemType);
                 itemWraps.add(temp);
                 baseItem.itemWraps.add(temp);
             }
             ItemWrap divider = new ItemWrap(baseItem, ItemType.DIVIDER);
             itemWraps.add(divider);
             baseItem.itemWraps.add(divider);
         }
         return itemWraps;
     }複製程式碼
    getTransformedItem方法將原始資料進行拆分,注意每種原始資料型別中都要加入divider模板,用於展示ListView的分割線。
  3. 根據不同的展示型別提供不同的view:
    public static View createItemView(ItemType itemType) {
         View view = null;
         BaseHolder baseHolder = null;
         switch (itemType) {
             case TITLE:
                 view = LayoutInflater.from(_Application.applicationContext).inflate(R.layout.title_item, null);
                 baseHolder = new TitleHolder();
                 break;
             case CARD:
                 view = LayoutInflater.from(_Application.applicationContext).inflate(R.layout.card_item, null);
                 baseHolder = new CardHolder();
                 break;
             case TEXT:
                 view = LayoutInflater.from(_Application.applicationContext).inflate(R.layout.text_item, null);
                 baseHolder = new TextHolder();
                 break;
             case IMAGE:
                 view = LayoutInflater.from(_Application.applicationContext).inflate(R.layout.image_item, null);
                 baseHolder = new ImageHolder();
                 break;
             case LINK:
                 view = LayoutInflater.from(_Application.applicationContext).inflate(R.layout.link_item, null);
                 baseHolder = new LinkHolder();
                 break;
             case HEADER:
                 view = LayoutInflater.from(_Application.applicationContext).inflate(R.layout.header_item, null);
                 baseHolder = new HeaderHolder();
                 break;
             case DIVIDER:
                 view = LayoutInflater.from(_Application.applicationContext).inflate(R.layout.divider_item, null);
                 baseHolder = new DividerHolder();
                 break;
         }
         baseHolder.setup(view);
         view.setTag(baseHolder);
         return view;
     }複製程式碼
  4. 按壓效果的實現
    對資料進行拆分後,有一個坑就是按壓效果的實現,這個時候listView中的每個item都不是一個完整的原始資料,要實現一個整體的按壓效果,demo的思路是:
    Android複雜資料流的“高效”渲染(二)
    Paste_Image.png

    當按壓任意一個view時,通知相應的item,改變item包含的所有view的狀態。具體實現時定義了一個BackgroundLinearLayout:
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_UP) {
             if (mListener != null) {
                 mListener.onStatePress(false);
             }
         } else if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             if (mListener != null) {
                 mListener.onStatePress(true);
             }
         } else if (ev.getAction() == MotionEvent.ACTION_CANCEL) {
             if (mListener != null) {
                 mListener.onStatePress(false);
             }
         }
         super.dispatchTouchEvent(ev);
         return true;
     }複製程式碼
    可以完成view按壓的回撥。
    但是每個holder需要如思路圖所示繫結到View,繫結可以在adapter的getview中完成。:
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
         ItemWrap itemWrap = (ItemWrap) getItem(position);
         if(convertView == null){
             convertView = ItemWrapHelper.getItemView(itemWrap.getItemType());
         }else {
             ((ItemWrap)(convertView.getTag(R.string.tag_key))).unBindView();
         }
         convertView.setTag(R.string.tag_key, itemWrap);
         BaseHolder baseHolder = (BaseHolder) convertView.getTag();
         baseHolder.render(itemWrap.getBaseItem());
         itemWrap.bindView(convertView);
         return convertView;
     }複製程式碼
    基本demo就完成了,按壓效果如圖:
    Android複雜資料流的“高效”渲染(二)
    Paste_Image.png

    總結

    由於主要展現功能,介面沒有進行太多調整,另外mock資料是複製了部分[one 一個]應用的資料,表示感謝。程式碼中有什麼問題,或者有什麼不合理的地方,感興趣的同學可以建立pull request,歡迎討論。專案地址ComplexDataStream

Other

歡迎關注公眾號wutongke,每天推送移動開發前沿技術文章:

Android複雜資料流的“高效”渲染(二)
wutongke

相關文章