開發中,常常會用到動態在ScrollView、LinearLayout裏addView的事,尤爲是ItemView同樣時,每次都要寫一大堆代碼 inflater 動態addView,很煩。javascript
還有就是在嵌套ListView、ScrollView時,想採用LinearLayout替代(這樣性能更佳,不明白的看一個控件搞定嵌套ListView),但動態addView步驟神煩。java
這個時候就開始期待,能不能有一種快速爲任意ViewGroup添加子View的東西。git
我以前爲此事特地寫過一篇LinearLayout封裝博文,封裝了一個控件使用。試圖一個控件搞定嵌套ListView。可是後來發現,採用繼承某個ViewGroup作這個事情不夠優雅 ,對代碼有侵入性,若是有其餘ViewGroup須要動態addView,就會寫重複的代碼 。github
前幾天有人在羣裏問,如何方便的給ScrollView動態添加不一樣種類型的childView,相似RecyclerView那樣。我以前的封裝因爲內部有一個簡單的重用機制,只支持單一ItemType,也不支持多種類型的childView。編程
那麼需求就來了:設計模式
除此以外,我還加入:緩存
OnItemClickListener
OnItemLongClickListener
本文就封裝了這麼一個東西。數據結構
核心:ide
PS:因此本文也算是填了以前的一個坑,在以前適配器模式博文文末,我就提到要寫一篇爲流式佈局增長Adapter的文章,做爲Adapter的實戰演練。使用本文封裝的Adapter天然能夠達到這一點。函數
因爲採用Adapter隔離ViewGroup和ItemView,在切換ViewGroup時,十分方便。
如:在需求讓你把一個HorizontalScrollView包裹的水平標籤轉換成流式佈局時,只須要在xml替換控件便可。Adapter將自動完成適配的工做。其餘代碼一句不用修改。
不BB了,先看看之後如何使用吧,夠不夠簡單粗暴。
轉載請標明出處:
gold.xitu.io/post/584d52…
本文出自:【張旭童的稀土掘金】(gold.xitu.io/user/56de21…)
代碼傳送門:喜歡的話,隨手點個star。多謝
github.com/mcxtzhang/a…
Adapter泛型傳入JavaBean,構造函數傳入數據集和layout佈局,一句代碼搞定:
//單一ItemView
ViewGroupUtils.addViews(mLinearLayout, new SingleAdapter<TestBean>(this, mDatas, R.layout.item_test) {
@Override
public void onBindView(ViewGroup parent, View itemView, TestBean data, int pos) {
Glide.with(LinearLayoutActivity.this)
.load(data.getAvatar())
.into((ImageView) itemView.findViewById(R.id.ivAvatar));
((TextView) itemView.findViewById(R.id.tvName)).setText(data.getName());
}
});複製代碼
效果:
多種Item類型分兩種狀況:
數據結構相同依然能夠給Adapter傳入泛型,避免強轉:
//多種ItemViewType,可是數據結構相同,能夠傳入數據結構泛型,避免強轉
ViewGroupUtils.addViews(linearLayout, new MulTypeAdapter<MulTypeBean>(this, initDatas()) {
@Override
public void onBindView(ViewGroup parent, View itemView, MulTypeBean data, int pos) {
((TextView) itemView.findViewById(R.id.tvWords)).setText(data.getName() + "");
Glide.with(MulTypeActivity.this)
.load(data.getAvatar())
.into((ImageView) itemView.findViewById(ivAvatar));
}
});複製代碼
效果:
若是數據結構不一樣,則不用傳入泛型,可是使用時須要強轉:
//多種Item類型:數據結構不一樣 不傳泛型了 使用時須要強轉javaBean,判斷ItemLayoutId
ViewGroupUtils.addViews((ViewGroup) findViewById(R.id.activity_mul_type_mul_bean), new MulTypeAdapter(this, datas) {
@Override
public void onBindView(ViewGroup parent, View itemView, IMulTypeHelper data, int pos) {
switch (data.getItemLayoutId()) {
case R.layout.item_mulbean_1:
MulBean1 mulBean1 = (MulBean1) data;
Glide.with(MulTypeMulBeanActivity.this)
.load(mulBean1.getUrl())
.into((ImageView) itemView);
break;
case R.layout.item_mulbean_2:
MulBean2 mulBean2 = (MulBean2) data;
TextView tv = (TextView) itemView;
tv.setText(mulBean2.getName());
}
}
});複製代碼
數據結構:
public class MulBean1 implements IMulTypeHelper {
private String url;
@Override
public int getItemLayoutId() {
return R.layout.item_mulbean_1;
}
}複製代碼
public class MulBean2 implements IMulTypeHelper {
private String name;
@Override
public int getItemLayoutId() {
return R.layout.item_mulbean_2;
}
}複製代碼
Item1佈局是一個ImageView,Item2佈局是一個TextView
效果:
item的點擊和長按等事件,有兩種方法設置,這裏以點擊事件爲例,長按事件同理:
在Adapter.onBindView()
方法裏能拿到ItemView,天然就能夠設置各類事件。相似RecyclerView。
在這裏設置優先級更高。緣由後文會提到。
@Override
public void onBindView(ViewGroup parent, View itemView, final MulTypeBean data, int pos) {
....
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(mContext, "onBindView裏設置:文字是:" + data.getName(), Toast.LENGTH_SHORT).show();
}
});
}複製代碼
能夠在ViewGroupUtils.addViews
直接做爲參數傳入.
也能夠用ViewGroupUtils.setOnItemClickListener()
設置 。
優先級比Adapter.onBindView()
裏設置低,緣由後文會提到。
//設置OnItemClickListener
OnItemClickListener onItemClickListener = new OnItemClickListener() {
@Override
public void onItemClick(ViewGroup parent, View itemView, int position) {
Toast.makeText(MulTypeActivity.this, "經過OnItemClickListener設置:" + position, Toast.LENGTH_SHORT).show();
}
};
//能夠在`ViewGroupUtils.addViews`直接做爲參數傳入.\
ViewGroupUtils.addViews(linearLayout, adapter ,onItemClickListener);
//或者 也能夠用`ViewGroupUtils.setOnItemClickListener()`設置
ViewGroupUtils.setOnItemClickListener(linearLayout,onItemClickListener);複製代碼
看起來仍是挺好的,嗯~至少我本身這麼以爲,我我的比較喜歡這種0耦合,每個庫都像可組裝拆卸的機關槍同樣,拿起來就用。而不是笨重功能繁多的重裝坦克。
搭配個人得意之做,每次必安利的史上集成最簡單側滑菜單控件。
效果以下:
無特殊設置,僅僅替換ViewGroup爲流式佈局,替換Item根佈局爲我擼的側滑菜單庫,能感覺到這種0耦合的庫的魅力了麼。23333333 。
下面就讓我手摸手帶你們實現它。
先看類圖。
先簡要歸納
咱們的頂層接口IViewGroupAdapter
暴露出兩個方法供ViewGroup使用。
ViewGroupUtils
是爲任意ViewGroup 動態addView的工具類,只依賴於 IViewGroupAdapter
接口便可完成工做。
BaseAdapter
是第二層,在這一層引入了數據集,用List<T>
保存。實現IViewGroupAdapter
的方法,重載一個三參數的getView()
方法,供子類去實現。
SingleAdapter
是第三層,一個簡化的Adapter,只支持單種Item,以LayoutId 構建View。實現getView()
方法,並暴露出onBindView()
供用戶快速使用。
MulTypeAdapter
也同處第三層,一個支持多種Item的Adapter。依賴IMulTypeHelper
接口,利用其getItemLayoutId()
方法去實現getView()
方法,並暴露出onBindView()
供用戶快速使用。
頂層接口,即IViewGroupAdapter
。
根據迪米特法則(最少知道原則),咱們應該抽象出一個頂層的接口,對ViewGroup暴露出最少的方法供使用。
咱們想一下,對於ViewGroup,它最少只須要哪些就能完成咱們的需求。
public interface IViewGroupAdapter {
/** * ViewGroup調用獲取ItemView * * @param parent * @param pos * @return */
View getView(ViewGroup parent, int pos);
/** * ViewGroup調用,獲得ItemCount * * @return */
int getCount();
}複製代碼
ok,代碼寫到這裏,後面的咱們暫且不提,咱們就能夠寫動態addView的工具類了。由於咱們的ViewGroup依賴的全部信息都由IViewGroupAdapter
這個接口提供了。
ViewGroupUtils
是爲任意ViewGroup 動態addView的工具類,不考慮點擊事件的狀況下,只依賴於 IViewGroupAdapter
接口便可完成工做。
以下編寫:
/** * 爲任意ViewGroup 添加ItemViews. * * @param viewGroup 必傳 * @param adapter 必傳,至少提供要add的View和須要add的count * @param removeViews 是否須要remove掉以前的Views */
public static void addViews(final ViewGroup viewGroup, IViewGroupAdapter adapter
, boolean removeViews) {
if (viewGroup == null || adapter == null) {
return;
}
//若是須要remove掉以前的Views
if (removeViews && viewGroup.getChildCount() > 0) {
viewGroup.removeAllViews();
}
//開始添加子Views,經過Adapter得到須要添加的Count
int count = adapter.getCount();
for (int i = 0; i < count; i++) {
//經過Adapter得到ItemView
View itemView = adapter.getView(viewGroup, i);
viewGroup.addView(itemView);
}
}複製代碼
如此便可完成 動態給任意ViewGroup addView 的工做。
不過咱們開頭提過,我仍是想引入ItemView的點擊和長按事件的。可是關於這兩個ItemListener,仍是有一些東西要考慮的。
爲ViewGroup提供OnItemClickListener
,有個問題須要考慮:
若是使用者調用了setOnItemClickListener
,且在Adapter
裏本身又對ItemView
設置了OnClickListener
,那麼究竟該觸發哪一個Listener
,即它們的優先級。
咱們不該該本身靠腦子想答案,仍是參照系統原有的設計比較好。既然ListView提供了OnItemClickListener
,那麼咱們參照它的設計來就行。
先說結論:經過參照ListView的源碼,以及實驗得知,對ItemView
的OnClickListener
優先級 > ViewGroup的OnItemClickListener
。
爲何?答案在源碼中,感興趣看,不感興趣直接跳過。
從AbsListView的onTouchEvent()
->onTouchUp()
->PerformClick
->performItemClick
-> AdapterView.performItemClick()
-> AdapterView.mOnItemClickListener
,便可找到答案。
這裏不詳細分析源碼,不是本文重點,簡單的說,從入口處是AbsListView的onTouchEvent()
,咱們能夠知道,ListView自己並無干預ItemView
的點擊事件(即沒有爲其設置OnClickListener
),是在ItemView
不消耗Touch事件時 才進行Item點擊事件的觸發。
所以若ItemView
設置了OnClickListener
,AbsListView的onTouchEvent()
將收不到MotionEvent.ACTION_UP
事件,於是也不會觸發OnItemClickListener
。因此這決定了ItemView
的OnClickListener
優先級高。
還有一個問題,咱們能夠經過View.hasOnClickListeners()
這個方法來判斷View是否設置了OnClickListener
,可是這個方法在API15才加入,爲了能兼容低版本,我採用了另外一種方法判斷,itemView.isClickable()
,若是true,我當作有點擊事件,若是false,我當作沒有。
其實在ListView中這個問題也是同樣的,itemView.isClickable()
爲true的話,點擊事件就被攔截住,不會分發至AbsListView的onTouchEvent()
裏了。因此咱們這麼寫是沒問題,而且是正確的。
既然如此,那麼咱們在程序中,應該以下編寫:
for (int i = 0; i < count; i++) {
View itemView = adapter.getView(viewGroup, i);
viewGroup.addView(itemView);
//添加點擊事件
if (null != onItemClickListener && !itemView.isClickable()) {
final int finalI = i;
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onItemClickListener.onItemClick(viewGroup, view, finalI);
}
});
}
//添加點擊事件
if (null != onItemLongClickListener && !itemView.isLongClickable()) {
final int finalI = i;
itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
return onItemLongClickListener.onItemLongClick(viewGroup, view, finalI);
}
});
}
}複製代碼
因此完整的addViews()
以下:
/** * 爲任意ViewGroup 添加ItemViews. * * @param viewGroup 必傳 * @param adapter 必傳,至少提供要add的View和須要add的count * @param removeViews 是否須要remove掉以前的Views * @param onItemClickListener Item點擊事件 * @param onItemLongClickListener Item長按事件 */
public static void addViews(final ViewGroup viewGroup, IViewGroupAdapter adapter
, boolean removeViews
, final OnItemClickListener onItemClickListener
, final OnItemLongClickListener onItemLongClickListener) {
if (viewGroup == null || adapter == null) {
return;
}
//若是須要remove掉以前的Views
if (removeViews && viewGroup.getChildCount() > 0) {
viewGroup.removeAllViews();
}
//開始添加子Views,經過Adapter得到須要添加的Count
int count = adapter.getCount();
for (int i = 0; i < count; i++) {
//經過Adapter得到ItemView
View itemView = adapter.getView(viewGroup, i);
viewGroup.addView(itemView);
//添加點擊事件,itemView以前沒有點擊事件纔會去設置
if (null != onItemClickListener && !itemView.isClickable()) {
final int finalI = i;
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onItemClickListener.onItemClick(viewGroup, view, finalI);
}
});
}
//添加長按事件itemView以前沒有長按事件纔會去設置
if (null != onItemLongClickListener && !itemView.isLongClickable()) {
final int finalI = i;
itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
return onItemLongClickListener.onItemLongClick(viewGroup, view, finalI);
}
});
}
}
}複製代碼
實際中,咱們可能不須要設置Listener,爲了快速使用,我又提供了兩個重載方法:
/** * 爲任意ViewGroup 添加ItemViews. * 而且會清除掉以前全部add過的View * * @param viewGroup 必傳 * @param adapter 必傳,至少提供要add的View和須要add的count */
public static void addViews(final ViewGroup viewGroup, IViewGroupAdapter adapter) {
addViews(viewGroup, adapter, true, null, null);
}
/** * 爲任意ViewGroup 添加ItemViews. * 而且會清除掉以前全部add過的View * * @param viewGroup 必傳 * @param adapter 必傳,至少提供要add的View和須要add的count * @param onItemClickListener Item點擊事件 */
public static void addViews(final ViewGroup viewGroup, IViewGroupAdapter adapter
, final OnItemClickListener onItemClickListener) {
addViews(viewGroup, adapter, true, onItemClickListener, null);
}複製代碼
若不在addViews()
裏設置ItemListener,也能夠經過setOnItemClickListener()
和setOnItemLongClickListener()
設置,不過這兩個方法必須在addViews()方法以後調用:
/** * 爲任意ViewGroup設置OnItemClickListener. * 該方法必須在addViews()方法以後調用,不然無效。 * 由於ItemView 必須被添加在ViewGroup裏才能遍歷到。 * 建議直接在addViews()方法裏傳入OnItemClickListener進行設置,性能更高 * * @param viewGroup * @param onItemClickListener */
public static void setOnItemClickListener(final ViewGroup viewGroup, final OnItemClickListener onItemClickListener) {
if (viewGroup == null || onItemClickListener == null) {
return;
}
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
final View itemView = viewGroup.getChildAt(i);
//itemView以前沒有點擊事件纔會去設置
if (null != itemView && !itemView.isClickable()) {
final int finalI = i;
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onItemClickListener.onItemClick(viewGroup, itemView, finalI);
}
});
}
}
}
/** * 爲任意ViewGroup設置OnItemLongClickListener. * 該方法必須在addViews()方法以後調用,不然無效。 * 由於ItemView 必須被添加在ViewGroup裏才能遍歷到。 * 建議直接在addViews()方法裏傳入OnItemLongClickListener進行設置,性能更高 * * @param viewGroup * @param onItemLongClickListener */
public static void setOnItemLongClickListener(final ViewGroup viewGroup, final OnItemLongClickListener onItemLongClickListener) {
if (viewGroup == null || onItemLongClickListener == null) {
return;
}
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
final View itemView = viewGroup.getChildAt(i);
//itemView以前沒有長按事件纔會去設置
if (null != itemView && !itemView.isLongClickable()) {
final int finalI = i;
itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
return onItemLongClickListener.onItemLongClick(viewGroup, itemView, finalI);
}
});
}
}
}複製代碼
終於到了咱們的重頭戲,Adapter。
BaseAdapter
是第二層,在這一層引入了數據集,用List<T>
保存。實現IViewGroupAdapter
的方法,重載一個三參數的getView()
方法,供子類去實現。
它和咱們平時寫的ListView、RecyclerView的Adapter就比較像了,我也是參照平時的寫法。
核心就是實現IViewGroupAdapter
的getView(ViewGroup parent, int pos)
方法,增長一個數據,工做轉交給三參數的getView(ViewGroup parent, int pos, T data)
方法。
子類應該 實現 getView(ViewGroup parent, int pos, T data)
方法,在其中inflate or new 出 ItemView,並綁定數據。
public abstract class BaseAdapter<T> implements IViewGroupAdapter {
protected List<T> mDatas;
protected Context mContext;
protected LayoutInflater mInflater;
/** * ViewGroup調用獲取ItemView,create bind一塊兒作 * * @param parent * @param pos * @return */
@Override
public View getView(ViewGroup parent, int pos) {
return getView(parent, pos, mDatas.get(pos));
}
/** * 實際的createItemView的地方 * * @param parent * @param pos * @param data * @return */
public abstract View getView(ViewGroup parent, int pos, T data);
/** * ViewGroup調用,獲得ItemCount * * @return */
@Override
public int getCount() {
return mDatas != null ? mDatas.size() : 0;
}
}複製代碼
SingleAdapter
是第三層,一個簡化的Adapter,只支持單種Item,以LayoutId 構建View。實現getView()
方法,並暴露出onBindView()
供用戶快速使用。
使用時,通常將數據結構的泛型傳入,配合構造函數傳入的ItemLayoutId使用。
它繼承自BaseAdapter
,因此它了實現getView(ViewGroup parent, int pos, T data)
方法。在根據傳入的itemLayoutId inflate出ItemView後,會回調onBindView(ViewGroup parent, View itemView, T data, int pos)
方法,並返回ItemView供ViewGroup使用。
在onBindView(ViewGroup parent, View itemView, T data, int pos)
方法裏,咱們完成數據綁定的工做。例如加載圖片,也能夠設置點擊事件。
public abstract class SingleAdapter<T> extends BaseAdapter<T> {
private int mItemLayoutId;
@Override
public View getView(ViewGroup parent, int pos, T data) {
//實現getView
View itemView = /*onCreateView(parent, pos)*/mInflater.inflate(mItemLayoutId, parent, false);
onBindView(parent, itemView, data, pos);
return itemView;
}
/** * 暴漏這個 讓外部bind數據 * * @param parent * @param itemView * @param data * @param pos */
public abstract void onBindView(ViewGroup parent, View itemView, T data, int pos);
}複製代碼
MulTypeAdapter
也同處第三層,一個支持多種Item的Adapter。依賴IMulTypeHelper
接口,利用其getItemLayoutId()
方法去實現getView()
方法,並暴露出onBindView()
供用戶快速使用。
public abstract class MulTypeAdapter<T extends IMulTypeHelper> extends BaseAdapter<T> {
@Override
public View getView(ViewGroup parent, int pos, T data) {
View itemView = mInflater.inflate(data.getItemLayoutId(), parent, false);
onBindView(parent, itemView, data, pos);
return itemView;
}
/** * 暴漏這個 讓外部bind數據 * * @param parent * @param itemView * @param data * @param pos */
public abstract void onBindView(ViewGroup parent, View itemView, T data, int pos);
}複製代碼
IMulTypeHelper
接口,一樣簡單:
public interface IMulTypeHelper {
int getItemLayoutId();
}複製代碼
代碼傳送門:喜歡的話,隨手點個star。多謝
github.com/mcxtzhang/a…
由於想支持任意ViewGroup,對ViewGroup代碼無侵入性,於是對部分功能進行了取捨,例如設置OnItemXXXListener
,若是採用繼承ViewGroup,嵌入代碼,能夠作到不強制addViews()
和setOnItemClickListener()
順序。
經過上文咱們能感覺到一些面向接口編程的奧義,例如咱們只須要設計好頂層的接口IViewGroupAdapter
,在沒寫剩下的xxxAdapter時,工具類均可以寫好,編譯經過。
這在之後擴展時,例如,我數據集不能用List來保存了,我多是多個List or Map 等等,只須要實現IViewGroupAdapter
接口便可定製一個Adapter。其餘代碼不需任何修改。
有人說過,設計模式怎麼學,就是先學完一遍全部的設計模式。而後再所有忘掉他們,只要記住SOLID原則便可。
我以爲頗有道理,就像我以前一直在糾結代理模式和裝飾者模式的區別,後來我想,編碼時,我管它是什麼模式,只要寫出來的是易維護的代碼便可。
onBindView()
的ItemView
->通用的ViewHolder
,這樣能夠少寫一些findViewById()
代碼一個用ScrollView很容易實現,但RecyclerView、ListView就沒法實現的動畫效果,仿淘寶會員中心等級動畫。凸顯爲全部ViewGroup增長Adapter模式的重要性。
感興趣能夠去閱讀
gold.xitu.io/post/584fbd…
轉載請標明出處:
gold.xitu.io/post/584d52…
本文出自:【張旭童的稀土掘金】(gold.xitu.io/user/56de21…)
代碼傳送門:喜歡的話,隨手點個star。多謝
github.com/mcxtzhang/a…