上篇Android ListView中複雜資料流的高效渲染文章中介紹如何高效利用ListView的快取進行渲染。之後有挺多同學有些疑惑,希望可以有一個demo,於是利用業餘時間把demo櫓出來了,如果有什麼問題大家可以評論或者在ComplexDataStream issue中提issue。這裡貼一下demo的地址:ComplexDataStream。另個人覺得這個思路實際增加了程式碼複雜度,在邏輯上並不高效,於是在題目中加了引號,但是實測一個複雜列表中可以節約10-20m的記憶體,這一點是很有誘惑力的。下面結合程式碼介紹一下。
程式碼結構
- Model
model中是所有的原始資料類,這裡為了方便,每種資料的名字實際包含了需要展示的模型:如CardWithTitleItem資料實際需要展示一個標題和一個卡片,HeaderImageCardItem需要展示一個頭部、圖片、卡片。
Adapter
Adapter中放置了adapter和各種型別的holder,這裡把多種資料型別拆分成了card、divider、header、image、link、text、title,併為每種型別設定了相應的佈局。Util
Util中設定如何transform資料到相應的展示模版,以及解決按壓效果的問題。
程式碼思路
我們的目的是將複雜的資料型別進行拆分,從而達到細顆粒的view複用,降低記憶體佔用。
- 確定拆分後的展示型別,這裡使用了一個enum型別:
public enum ItemType { TITLE, CARD, HEADER, IMAGE, TEXT, LINK, DIVIDER }複製程式碼
- transform資料到模板,拆分後一個資料型別對應多個模板,這裡我們使用hashmap建立資料到模板的影射關係:
private static Map<Class, List<ItemType>> map = new HashMap<>();複製程式碼
getTransformedItem方法將原始資料進行拆分,注意每種原始資料型別中都要加入divider模板,用於展示ListView的分割線。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; }複製程式碼
- 根據不同的展示型別提供不同的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; }複製程式碼
- 按壓效果的實現
對資料進行拆分後,有一個坑就是按壓效果的實現,這個時候listView中的每個item都不是一個完整的原始資料,要實現一個整體的按壓效果,demo的思路是:
當按壓任意一個view時,通知相應的item,改變item包含的所有view的狀態。具體實現時定義了一個BackgroundLinearLayout:
可以完成view按壓的回撥。@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; }複製程式碼
但是每個holder需要如思路圖所示繫結到View,繫結可以在adapter的getview中完成。:
基本demo就完成了,按壓效果如圖:@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; }複製程式碼
總結
由於主要展現功能,介面沒有進行太多調整,另外mock資料是複製了部分[one 一個]應用的資料,表示感謝。程式碼中有什麼問題,或者有什麼不合理的地方,感興趣的同學可以建立pull request,歡迎討論。專案地址ComplexDataStream。
Other
歡迎關注公眾號wutongke,每天推送移動開發前沿技術文章: