編輯:關於Android編程
之所以要把 ItemType 的封裝單獨拉出一篇文章,是因為前面兩篇分別是針對 ListAdapter 和 RecyclerAdapter,而 ItemType 封裝的思路則都是一樣的,基本上沒有區別可言。另外一方面,我也覺得一篇文章的篇幅還是應該有點限制,不然我自己看著都沒耐心。
首先我覺得應該感謝一下鴻揚大神,很大程度上我對 ItemType 的處理參考了他的 baseAdapter 項目,雖然我仍然堅持我對某一個細節的處理,但是他的項目確實給了我不少靈感和參考。後面也會針對這一點進行對比。
在上一期的 RecyclerAdapter 的基礎上,做最小修改實現 ItemType 的代碼大概會是這樣的,也是我最早實現的方法
adapter = new RecyclerAdapter- (this, dataSource) { @Override public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == 0) { return new RecyclerViewHolder(mInflater.inflate(layout0, parent, false)); } else { return new RecyclerViewHolder(mInflater.inflate(layout1, parent, false)); } } @Override protected void onBindData(RecyclerViewHolder holder, Item data, int position) { if (getItemViewType(position) == 0) { //TODO } else { //TODO } } @Override protected int getItemViewType(int position, Item data) { return data.type; } };
可以看出,在不對 RecyclerAdapter 大改的條件下, 至少需要重寫 onCreateViewHolder、onBindData、getItemViewType 三個方法,而且這還是我已經對 RecyclerAdapter 做出了一定程度的修改的情況:
1.構造器重載,可以不傳入布局 id
2.添加getItemViewType(int, Item) 方法,避免手動寫 dataSource.get(position) 這樣的意大利面代碼
我還嘗試過傳入一個 Map
class MultiTypeAdapter extends ListAdapter- { public MultiTypeAdapter(Context context, List
- data, Map
layouts) { super(context, data, layouts); } @Override protected void setItem(View convertView, Item data, int position) { if (getItemViewType(position) == Item.TYPE_ONE) { setTypeOneItem(convertView, data, position); } else { setTypeTwoItem(convertView, data, position); } } private void setTypeOneItem(View convertView, Item data, int position) { //TODO } private void setTypeTwoItem(View convertView, Item data, int position) { //TODO } @Override protected int getItemViewType(int position, Item data) { return data.type; } }
Maplayouts = new HashMap<>(); layouts.put(Item.TYPE_ONE, R.layout.item_multi_type_one); layouts.put(Item.TYPE_TWO, R.layout.item_multi_type_two); adapter = new MultiTypeAdapter(this, dataSource, layouts);
從代碼可以看出,確實減少了我們去處理什麼 type 加載什麼布局的工作,但是無可避免的在綁定的時候卻不得不再去判斷一次 type,然後做不同的事情。說好聽一點,這並不符合我們期望的綁定方法只做綁定的事情,也擔起了分類的責任;說簡單一點,那就是這代碼不優雅~
在不同的 Activity 中,長得一樣、邏輯也差不多的 ListView、RecyclerView 可以使用同一個 Adapter 就完成 Item 的展示。但是考慮到 ItemType 的話,事情就會復雜些許。例如:我有一個擁有類型1和類型2的 ListView 取名為 A,我有一個擁有類型2和類型3的 ListView 取名為 B,如果按照上面的做法,那麼我只能寫兩個 Adapter 。如果把情況考慮復雜一點,A 對應123,B 對應234,那麼 A 和 B 他們有一大半的 Item 類型是一致的,這意味著我們的重復代碼會很多。再考慮極端一些呢?……
可是,A 和 B 的不同導致他們幾乎不得不使用不同的 Adapter,那我們應該怎麼辦呢?
我個人有一個觀點:對 Adapter 的復用實質上是為了對 Item 的復用,這裡的 Item 包括布局、數據綁定、事件監聽等。既然 Adapter 一定是不同的,為了實現 Item 的復用,我們是不是應該考慮把布局、數據綁定、事件監聽等處理從 Adapter 中剝離出來?
於是 Delegate 類的價值就產生出來了,我們先看看鴻揚大神的代碼:
/** * Created by zhy on 16/6/22. */ public interface ItemViewDelegate{ int getItemViewLayoutId(); boolean isForViewType(T item, int position); void convert(ViewHolder holder, T t, int position); }
三個方法依次為:
1.向 Adapter 提供布局文件的 id
2.判斷傳入的 item 是不是自己應該處理的類型
3.綁定 holder 和數據
忽略掉 Adapter 的具體封裝,使用更是簡單無比
MultiItemTypeAdapter adapter = new MultiItemTypeAdapter(this,mDatas); adapter.addItemViewDelegate(new MsgSendItemDelagate()); adapter.addItemViewDelegate(new MsgComingItemDelagate());
每種Item類型對應一個ItemViewDelegete,例如
public class MsgComingItemDelagate implements ItemViewDelegate{ @Override public int getItemViewLayoutId() { return R.layout.main_chat_from_msg; } @Override public boolean isForViewType(ChatMessage item, int position) { return item.isComMeg(); } @Override public void convert(ViewHolder holder, ChatMessage chatMessage, int position) { holder.setText(R.id.chat_from_content, chatMessage.getContent()); holder.setText(R.id.chat_from_name, chatMessage.getName()); holder.setImageResource(R.id.chat_from_icon, chatMessage.getIcon()); } }
鴻揚大神的思路,大致上是在 adapter 調用 getItemType 的時候,遍歷所有 delegate,調用 delegate 的 isForViewType 來判斷是否是自己的類型,如果是的話就停止遍歷,返回這個 delegate。
public int getItemViewType(T item, int position) { int delegatesCount = delegates.size(); for (int i = delegatesCount - 1; i >= 0; i--) { ItemViewDelegatedelegate = delegates.valueAt(i); if (delegate.isForViewType( item, position)) { return delegates.keyAt(i); } } throw new IllegalArgumentException( "No ItemViewDelegate added that matches position=" + position + " in data source"); }
有了符合條件的 delegate,就可以將渲染 item 的任務交給它了。於是就有了上面三行代碼實現一個多類型的 Adapter 如此精簡的代碼。
代碼詳情請至鴻揚大神的 github 查看:Android/baseAdapter">https://github.com/hongyangAndroid/baseAdapter
請原諒我雞蛋裡面挑骨頭。
雖然鴻揚大神提供的方案可以說是精簡到了極致,但是在復用方面我個人是持懷疑態度的。鴻揚大神的 Delegate 是自己決定我是不是屬於這個類型。回到上面 A、B 兩個 ListView 的例子中,我們能保證 A、B 中使用類型2和3的條件是一樣的嗎?不能,所以我們只能去修改 isForViewType 方法來兼容兩種條件;而當這兩種條件之間無法兼容的時候,我們只能讓 A、B 中的同一種類型使用不同的 Delegate 類,即便他們長得一樣,交互也一樣。
即:當業務發生變化的時候,我可能會需要去修改 Delegate 類,或者增加僅有 isForViewType 實現不同的類。在比較苛刻的條件下,這並沒有真正的做到 Item 復用。
而我所期望的 Delegate,什麼時候用它,什麼條件下用它,這不應該由它自己去決定,因為 Delegate 並不懂業務,我也不希望它和業務耦合在一起,我只是希望它能夠根據傳入的數據對象執行綁定工作而已。
所以我的 Delegate 是這樣的:
public interface AdapterDelegate{ int getLayoutId(); void bind(RecyclerViewHolder holder, T data, int position); }
因為 delegate 不負責類型的判斷,所以使用時稍微復雜一些:
adapter = new MultiTypeRecyclerAdapter(this, dataSource) { @Override protected int getItemViewType(MultiTypeItem data) { return data.type; } }; adapter.addDelegate(MultiTypeItem.TYPE_ONE, new TypeOneDelegate()) .addDelegate(MultiTypeItem.TYPE_TWO, new TypeTwoDelegate());
首先,adapter 添加 delegate 的時候是以鍵值對的形式添加的,可以指定 delegate 去處理哪一種類型;
其次,adapter 需要重寫一個 getItemViewType 方法,告訴 adapter 判斷類型的依據。
這樣子, adapter 就知道了什麼時候去使用哪個 delegate。而當業務發生變化,但是 UI 沒改的情況下,我不需要改動任何一個 delegate,而是改 adapter 定義的代碼。
附上 MultiTypeRecyclerAdapter 的代碼:
public class MultiTypeRecyclerAdapterextends RecyclerView.Adapter { protected Context mContext; protected List mData; protected LayoutInflater mInflater; protected SparseArray> delegates = new SparseArray<>(); protected int layoutRes; public MultiTypeRecyclerAdapter(Context context) { this.mData = new ArrayList<>(); this.mContext = context; this.mInflater = LayoutInflater.from(mContext); } public MultiTypeRecyclerAdapter(Context context, List data) { this.mData = data; this.mContext = context; this.mInflater = LayoutInflater.from(mContext); } public MultiTypeRecyclerAdapter(Context context, List data, int layoutRes) { this.mData = data; this.mContext = context; this.mInflater = LayoutInflater.from(mContext); this.layoutRes = layoutRes; } public MultiTypeRecyclerAdapter(Context context, List data, AdapterDelegate delegate) { this.mData = data; this.mContext = context; this.mInflater = LayoutInflater.from(mContext); delegates.put(0, delegate); } public MultiTypeRecyclerAdapter addDelegate(int type, AdapterDelegate delegate) { delegates.put(type, delegate); return this; } public MultiTypeRecyclerAdapter addDelegate(AdapterDelegate delegate) { return addDelegate(0, delegate); } public void refresh(List data) { try { this.mData = data; notifyDataSetChanged(); } catch (Exception e) { e.printStackTrace(); } } @Override public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (delegates.size() == 0) { return new RecyclerViewHolder(mInflater.inflate(layoutRes, parent, false)); } else { return new RecyclerViewHolder(mInflater.inflate(delegates.get(viewType).getLayoutId(), parent, false)); } } @Override public void onBindViewHolder(RecyclerViewHolder holder, int position) { if (delegates.size() == 0) { bind(holder, mData.get(position), position); } else { AdapterDelegate delegate = delegates.get(getItemViewType(position)); delegate.bind(holder, mData.get(position), position); } } @Override public int getItemCount() { return mData.size(); } @Override public int getItemViewType(int position) { return getItemViewType(mData.get(position)); } /** * 由子類處理,默認返回 0 * @param data * @return */ protected int getItemViewType(T data) { return 0; } /** * 單類型時子類需實現的方法 * 處理綁定 view * @param holder * @param data * @param position */ protected void bind(RecyclerViewHolder holder, T data, int position) { } }
最後,再次感謝鴻揚大神,他的 baseAdapter 項目更為成熟,包含的功能也更多,讓我受益匪淺。
代碼詳見:https://github.com/neverwoodsS/zy-open
第一步、效果展示圖1、藍色的進度條圖2、紅色的進度條圖3、多條顏色不同的進度條圖4、多條顏色不同的進度條 版權聲明:本文為【歐陽鵬】原創文章,歡迎轉載,轉載請注明出處!
當前很多APP都有短信驗證的功能,如:帳號與手機號綁定的時候,通過短信驗證的方式確認身份。那麼該如何實現這個功能呢。先簡單說一下流程第一步:獲取短信驗證碼SDK第二步:導
Android平台的誕生為手機智能化的普及立下汗馬功勞,但其最大的缺點也越來越凸顯,那就是碎片化嚴重:設備繁多、品牌眾多、版本各異,芯片、攝像頭、分辨率不統一等等,這些都
對於側滑刪除已經是見慣不慣的了,我也一直有寫類似QQ那樣的側滑刪除控件的想法,雖然研究一段時間的自定義View,然對自定義ViewGroup實戰還是較少,並且側滑刪除還要