編輯:關於Android編程
ScrollView裡嵌套ListView,一直是Android開發者(反正至少是我們組)最討厭的設計之一,完美打破ListView(RecyclerView)的復用機制,成功的將Native頁面變成一個又臭又長的H5網頁效果,但由於這種設計需求在我司項目實在太多見,無奈之下,我還是決定封裝一下,畢竟,一個項目裡同樣的代碼寫第二遍的程序員都不是好的聖斗士。但是我真的是拒絕的 !拒絕的!拒絕的!真的不喜歡這種界面:
還拿我前兩天做的這個項目來說吧,如上圖,技能認可是一個“ListView”,工作經歷是一個“ListView”,每個”ListView”的Item裡還會有評論,評論又是一個“ListView”,項目經歷 教育經歷與此類似。。世界上最恐怖的事,不是ListView套ListView,是ListView套的ListView,裡面還要繼續嵌套ListView。。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxociAvPg0KPGgyIGlkPQ=="二-競品分析">二 競品分析:
對於以上情況, 由於需要在ScrollView中嵌套ListView ,或者ListView中嵌套ListView….總結就是要嵌套ListView在另外的可以滑動的ViewGroup中,這就有兩個問題,
一,ListView和ViewGroup的滑動沖突。
二,ListView並不是全部展開的(View是復用的,ListView最多只有一屏的高度)。
市面上的解決方案,常見三種:
1、手動遍歷子View,設置ListView高度(麻煩,且Item的根布局是RelativeLayout的時候無法測量,在android系統版本在17級以下(包含17的時候),RelativeLayout.measure(w,h)時,會出現空指針,只能外層再套一個其他Layout,這是硬傷)
2、通過重寫ListView的onMeasure()方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); }
以前項目裡常用這個,最容易百度出來的“最優”解,代碼量最少,那時年少的我看到它是如獲至寶的,因為那篇文章裡口口聲聲告訴我,這樣的話View還可以復用,真的很”優雅”。
但我經過實戰發現Adapter的getView()會被重復調用多次,如果嵌套兩層,getView()倍數調用,太傷性能,它根本不能復用View,不僅不復用,反而變本加厲。
故棄用之。下一節中會提供證據,一定讓你李菊福。
而且在某些極端情況下,例如每個Item的高度不一樣,這個ListView的高度計算偶爾會不准確。
3、使用LinearLayout模擬ListView(寫起來麻煩,inflate 的死去活來,但無明顯缺點。
一開始我是拒絕這種方案的,太傻啦,自己inflate addView findViewById 多蠢,我有方法2 搭配CommonAdapter ViewHolder等工具類,要他何用。
但是在我知道方法2的真面目後,我只能選用本方法,它至少不會多次調用getView(),重復渲染視圖,反正View的復用機制已經被打破,使用ListView不再有任何意義。
So本文就是基於此種思路,封裝一下固定代碼,方便二次快速使用,且盡量的優化,一定程度上提高性能)
抽象封裝往LinearLayout裡inflate,addView的過程,暴漏出綁定數據的方法,並一定程度上考慮性能,緩存View。
在此基礎上,利用ViewHolder 思想,盡量避免每次刷新都走findViewById這些耗性能的方法。
這種頁面往往需要刷新,最無腦的辦法就是removeAllViews(),簡單粗暴,啥都不考慮,用戶體驗將會變成,刷新時閃一下,很差,因為View全部要inflate,addView,findViewById一遍。
所以我們在封裝的NestFullListView裡盡力避免刷新時 View的inflate addView,
在ViewHolder 盡力避免刷新時 findViewById();
本節代碼極其簡單,沒有營養,只為驗證,不具有參考價值,故注釋張 不再細細講解闡述,請大家光速閱讀。
布局如下:一個簡單的ScrollView裡面放一個重寫onMeasure()方法的ListView,兩個按鈕用來添加刪除數據源,
ListViewForScrollView.java 代碼如下:
public class ListViewForScrollView extends ListView{ public ListViewForScrollView(Context context) { super(context); } public ListViewForScrollView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); } }
只是重寫了onMeasure()方法 讓其全部展開。
測試Activity代碼:
/** * 本類用於驗證重寫onMeasure()方法的ListView,性能有多低。 * getView會被重復調用多次 */ public class ListViewActivity extends AppCompatActivity { private static final String TAG = "zxt/FullListView"; private ListmDatas; private ListViewForScrollView listViewForScrollView; private LvAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_list_view); initDatas(); listViewForScrollView = (ListViewForScrollView) findViewById(R.id.lv); listViewForScrollView.setAdapter(mAdapter = new LvAdapter(mDatas, this)); } private void initDatas() { int i = 0; mDatas = new ArrayList<>(); ArrayList nestBeen = new ArrayList<>(); nestBeen.add(new NestBean("http://jiangsu.china.com.cn/uploadfile/2015/0827/1440653790186574.jpg")); mDatas.add(new TestBean((i++) + "", "http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg", nestBeen)); nestBeen = new ArrayList<>(); nestBeen.add(new NestBean("http://imgs.ebrun.com/resources/2016_03/2016_03_24/201603244791458784582125_origin.jpg")); nestBeen.add(new NestBean("http://www.wccdaily.com.cn/hxdsb/20151204/6f443028313f1888b7a9fb19549d6ef6.jpg")); mDatas.add(new TestBean((i++) + "", "http://fudaoquan.com/wp-content/uploads/2016/04/wanghong.jpg", nestBeen)); nestBeen = new ArrayList<>(); nestBeen.add(new NestBean("http://img.mp.itc.cn/upload/20160427/316a154e56684a59b1e81df03a0860c4_th.png")); nestBeen.add(new NestBean("http://cdn.duitang.com/uploads/item/201509/17/20150917161810_exXGU.jpeg")); mDatas.add(new TestBean((i++) + "", "http://imgs.ebrun.com/resources/2016_03/2016_03_25/201603259771458878793312_origin.jpg", nestBeen)); mDatas.add(new TestBean((i++) + "", "http://p14.go007.com/2014_11_02_05/a03541088cce31b8_1.jpg")); mDatas.add(new TestBean((i++) + "", "http://news.k618.cn/tech/201604/W020160407281077548026.jpg")); mDatas.add(new TestBean((i++) + "", "http://www.kejik.com/image/1460343965520.jpg")); mDatas.add(new TestBean((i++) + "", "http://cn.chinadaily.com.cn/img/attachement/jpg/site1/20160318/eca86bd77be61855f1b81c.jpg")); mDatas.add(new TestBean((i++) + "", "http://imgs.ebrun.com/resources/2016_04/2016_04_12/201604124411460430531500.jpg")); mDatas.add(new TestBean((i++) + "", "http://imgs.ebrun.com/resources/2016_04/2016_04_24/201604244971461460826484_origin.jpeg")); mDatas.add(new TestBean((i++) + "", "http://www.lnmoto.cn/bbs/data/attachment/forum/201408/12/074018gshshia3is1cw3sg.jpg")); } public void add(View view) { mDatas.add(new TestBean("add", "http://finance.gucheng.com/UploadFiles_7830/201603/2016032110220685.jpg")); mAdapter.notifyDataSetChanged(); } public void del(View view) { mDatas.remove(mDatas.size() - 1); mAdapter.notifyDataSetChanged(); } }
基礎工作准備就緒,先來看只嵌套一層ListView的getView()方法執行的次數:
item是這樣滴:
Adapter是這樣滴:
/** * 介紹:嵌套第一層Adapter * 本類用於驗證重寫onMeasure()方法的ListView,性能有多低。 * getView會被重復調用多次 * 作者:zhangxutong * 郵箱:[email protected] * 時間: 2016/9/10. */ public class LvAdapter extends BaseAdapter { ... @Override public View getView(int position, View convertView, ViewGroup parent) { Log.d(TAG, "嵌套第1層的 getView() called with: position = [" + position + "], convertView = [" + convertView + "], parent = [" + parent + "]"); LvViewHolder holder; if (null == convertView) { convertView = mInflater.inflate(R.layout.item_list_view, parent, false); holder = new LvViewHolder(); holder.tv = (TextView) convertView.findViewById(R.id.tv); holder.iv = (ImageView) convertView.findViewById(R.id.iv); convertView.setTag(holder); } else { holder = (LvViewHolder) convertView.getTag(); } TestBean testBean = mDatas.get(position); holder.tv.setText(testBean.getName()); Glide.with(mContext) .load(testBean.getUrl()) .into(holder.iv); return convertView; } private static class LvViewHolder { TextView tv; ImageView iv; } }
UI美如畫:
那麼log裡getView()執行了多少次呢?如圖:
我們有九個Item,大概執行了9*7 = 63次吧,這個getView()執行次數好像和數據源的數量也有關系,但為什麼會循環的走了N遍,我沒有深究,我只知道!我被嚇壞了。 而且 每當你點擊add del 增刪數據源,刷新整個ListView的時候,這些getView()又會瘋狂的走幾十遍,有興趣的自己下載DEMO驗證。。。
膽子小的已經不願意再繼續看這一節了,但是我滿足膽子大的,別忘了 我開頭放的那張圖,評論可是ListViewForScrollView裡在嵌套一個ListViewForScrollView,嗯,那麼繼續:
item變成這樣:
Adapter變成這樣:
@Override public View getView(int position, View convertView, ViewGroup parent) { Log.d(TAG, "嵌套第1層的 getView() called with: position = [" + position + "], convertView = [" + convertView + "], parent = [" + parent + "]"); LvViewHolder holder; if (null == convertView) { convertView = mInflater.inflate(R.layout.item_list_view, parent, false); holder = new LvViewHolder(); holder.tv = (TextView) convertView.findViewById(R.id.tv); holder.iv = (ImageView) convertView.findViewById(R.id.iv); holder.lv = (ListViewForScrollView) convertView.findViewById(R.id.lv2); convertView.setTag(holder); } else { holder = (LvViewHolder) convertView.getTag(); } TestBean testBean = mDatas.get(position); holder.tv.setText(testBean.getName()); Glide.with(mContext) .load(testBean.getUrl()) .into(holder.iv); holder.lv.setAdapter(new NestAdapter(testBean.getNest(), mContext)); return convertView; } private static class LvViewHolder { TextView tv; ImageView iv; ListViewForScrollView lv; }
新Adapter這樣:
/** * 介紹:嵌套第二層Adapter * 本類用於驗證重寫onMeasure()方法的ListView,性能有多低。 * getView會被重復調用多次 * 作者:zhangxutong * 郵箱:[email protected] * 時間: 2016/9/10. */ public class NestAdapter extends BaseAdapter { ..... @Override public View getView(int position, View convertView, ViewGroup parent) { Log.d(TAG, "嵌套第二層的 getView() called with: position = [" + position + "], convertView = [" + convertView + "], parent = [" + parent + "]"); NestViewHolder holder; if (null == convertView) { convertView = mInflater.inflate(R.layout.item_nest_lv, parent, false); holder = new NestViewHolder(); holder.nestIv = (ImageView) convertView.findViewById(R.id.nestIv); convertView.setTag(holder); } else { holder = (NestViewHolder) convertView.getTag(); } NestBean nestBean = mDatas.get(position); Glide.with(mContext) .load(nestBean.getUrl()) .into(holder.nestIv); return convertView; } private static class NestViewHolder { ImageView nestIv; } }
UI美如畫二:
getView()次數:
當我第一次見到getView()執行這麼多次時,我是被嚇壞的,有人管管這個ListView嗎?他瘋了嗎?getView()不要錢嗎?執行這麼多次?
UI美如畫,getView()次數是丑成渣啊!
有興趣下載文末Demo自行驗證,我只有一個請求,。
那麼當我一開始被嚇壞的時候,我決定不這麼做了,我就老老實實的用LinearLayout然後遍歷數據源,往裡addView().:
核心代碼如下:
LinearLayout container= (LinearLayout)findViewById(R.id.xxxx); container.removeAllViews(); for (TestBean bean: mDatas) { LinearLayout item= (LinearLayout) mInflater.inflate(R.layout.xxxx, container, false); TextView tvName = (TextView) skillContent.findViewById(R.id.tvName); tvName.setText(skillInfoBean.getSkill_name()); llSkillContent.addView(skillContent); }
在布局裡添加一個LinearLayout替代ListViewForScrollView,
然後遍歷數據源,inflate出這些item,填充數據,
利用LinearLayout.addView(item),將item塞進去。
值得注意的是,每次遍歷數據源塞item的時候,要注意container.removeAllViews();,
否則刷新界面的時候,view會重復增加在LinearLayout的尾部。
2 成長期
這麼做,性能是得到了一定程度的緩解,至少是不會重復執行getView()方法了。
可是這種寫法,使用過的朋友肯定知道,其實代碼量是比使用ListViewForScrollView多的,而且都是重復的沒有意義的代碼。尤其在需要嵌套兩層ListView效果的時候,代碼爆炸。
當項目裡寫多了這種代碼的時候,我就開始厭倦它了。我要更簡單的用!
於是它進化了:
進化後使用方法:
nestFullListView = (NestFullListView) findViewById(R.id.cstFullShowListView);
nestFullListView.setAdapter(new FullListViewAdapter(R.layout.item_lv, mDatas) {
@Override
void onBind(int pos, TestBean testBean, View v) {
TextView tv = (TextView) v.findViewById(R.id.tv);
tv.setText(testBean.getName());
}
});
//pos是位置,第二個參數是數據,第三個參數是ItemView,
void onBind(int pos, TestBean testBean, View v)
給Adapter傳入item的layoutId,以及數據源後,我們只需要關注核心的數據綁定細節,其他完全不管。
增刪數據時,如下調用:
public void add(View view) {
mDatas.add(new TestBean("add", "http://finance.gucheng.com/UploadFiles_7830/201603/2016032110220685.jpg"));
nestFullListView.updateUI();
}
public void del(View view) {
mDatas.remove(mDatas.size() - 1);
nestFullListView.updateUI();
}
只要調用NestFullListView的updateUI()即可。
NestFullListView如下:
/**
* 介紹:完全伸展開的ListView(LinearLayout)
* 作者:zhangxutong
* 郵箱:[email protected]
* 時間: 2016/9/9.
*/
public class NestFullListView extends LinearLayout {
private LayoutInflater mInflater;
public NestFullListView(Context context) {
this(context, null);
}
public NestFullListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NestFullListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mInflater = LayoutInflater.from(context);
setOrientation(VERTICAL);
}
private FullListViewAdapter mAdapter;
/**
* 外部調用 同時刷新視圖
*
* @param mAdapter
*/
public void setAdapter(FullListViewAdapter mAdapter) {
this.mAdapter = mAdapter;
updateUI();
}
public void updateUI() {
removeAllViews();
if (null != mAdapter) {
if (null != mAdapter.getDatas() && !mAdapter.getDatas().isEmpty()) {
for (int i = 0; i < mAdapter.getDatas().size(); i++) {
View v = mInflater.inflate(mAdapter.getItemLayoutId(), this, false);
mAdapter.onBind(i, v);
addView(v);
}
}
}
}
}
代碼也很簡單,
1 初始化時設置LinearLayout的布局方向為豎直,
2 對外暴漏一個setAdapter()方法,
3 每次設置完Adapter後,自動調用updateUI() 方法進行視圖渲染,
4 updateUI() 時,先removeAllViews(),
5 然後從Adapter裡拿到數據源,和itemLayoutId, inflate出這個Item。
6 回調Adapter的onBind()方法。
7 add剛剛inflate的這個View進LinearLayout裡。
Adapter如下:
/**
* 介紹:完全伸展開的ListView的適配器
* 作者:zhangxutong
* 郵箱:[email protected]
* 時間: 16/09/09.
*/
public abstract class FullListViewAdapter {
private int mItemLayoutId;//看名字
private List mDatas;//數據源
public FullListViewAdapter(int mItemLayoutId, List mDatas) {
this.mItemLayoutId = mItemLayoutId;
this.mDatas = mDatas;
}
/**
* 被FullListView調用
*
* @param i
* @param v
*/
public void onBind(int i, View v) {
//回調bind方法,多傳一個data過去
onBind(i, mDatas.get(i), v);
}
/**
* 數據綁定方法
*
* @param pos 位置
* @param t 數據
* @param v ItemView
*/
abstract void onBind(int pos, T t, View v);
public int getItemLayoutId() {
return mItemLayoutId;
}
public void setItemLayoutId(int mItemLayoutId) {
this.mItemLayoutId = mItemLayoutId;
}
public List getDatas() {
return mDatas;
}
public void setDatas(List mDatas) {
this.mDatas = mDatas;
}
}
Adapter內存儲數據源和ItemLayoutId,
暴漏void onBind(int i, View v)供NestFullListView使用,
並且在這個方法裡,多傳一個數據data,回調abstract void onBind(int pos, T t, View v);
該方法就是我們需要繼承實現的方法,在裡面完成數據的綁定操作即可。
3 成熟期
經過成長期的封裝後,我們使用起來已經很方便了,可是我感覺它還是不太好:
每次updateUI() 時,它總是無腦的removeAllViews();,
如果新的datas的數量並沒有變,我們界面上所有的View都是可以復用的,
如果新的datas的數量變化不大,我們可以動態的增刪幾個View,沒必要無腦全部remove掉。
這樣可以最大可能減少inflate ,addView的操作,提高性能。
所以我又改寫了NestFullListView類:
public class NestFullListView extends LinearLayout {
private LayoutInflater mInflater;
private List mViewCahces;//緩存ItemView的List,按照add的順序緩存,
//...省略和成長期相同代碼
private void init(Context context) {
mInflater = LayoutInflater.from(context);
mViewCahces = new ArrayList();
setOrientation(VERTICAL);
}
//...省略和成長期相同代碼
public void updateUI() {
if (null != mAdapter) {
if (null != mAdapter.getDatas() && !mAdapter.getDatas().isEmpty()) {
//數據源有數據
if (mAdapter.getDatas().size() > getChildCount()) {//數據源大於現有子View不清空
} else if (mAdapter.getDatas().size() < getChildCount()) {//數據源小於現有子View,刪除後面多的
removeViews(mAdapter.getDatas().size(), getChildCount() - mAdapter.getDatas().size());
//刪除View也清緩存
while (mViewCahces.size() > mAdapter.getDatas().size()) {
mViewCahces.remove(mViewCahces.size() - 1);
}
}
for (int i = 0; i < mAdapter.getDatas().size(); i++) {
View v;
if (mViewCahces.size() - 1 >= i) {//說明有緩存,不用inflate,否則inflate
v = mViewCahces.get(i);
} else {
v = mInflater.inflate(mAdapter.getItemLayoutId(), this, false);
mViewCahces.add(v);//inflate 出來後 add進來緩存
}
mAdapter.onBind(i, v);
//如果View沒有父控件 添加
if (null == v.getParent()) {
this.addView(v);
}
}
} else {
removeAllViews();//數據源沒數據 清空視圖
}
} else {
removeAllViews();//適配器為空 清空視圖
}
}
}
增加一個變量
private List
每次updateUI()時,如果是異常情況:適配器為空 清空視圖,數據源沒數據 清空視圖
那麼數據源有數據的情況下,比較數據源的size 和現在子View(ItemView)的size,
如果數據源大於現有子View,說明屏幕上的View不夠用,當然不remove子View,也不用清緩存。
如果數據源小於現有子View,刪除尾部多的子View,清理多余緩存的ItemView,
遍歷數據源,比較i(postion)和viewCaches的size,
如果緩存不夠就inflate一個新View,
如果緩存有,就取出緩存的View。
回調Adapter的onBind方法,
判斷這個View有沒有父控件,
如果View沒有父控件 才addView()。
(後話,文章寫到這裡時,我才發現這裡本不需要viewCache,是我當時的時候思路不太對,可以通過LinearLayout.getChildAt()獲取LinearLayout裡的childView。 不過已不重要,因為成熟期,為了連findViewById()方法也盡可能的減少,引入了ViewHolder,是需要這麼一個ViewHolderCache的。)
4 完全體
成熟期裡,我們盡可能的避免了View的inflate,addView()操作。可是我們都知道,findViewById()的操作也是很費時的,能否像RecyclerView幾兄弟….那樣,引入ViewHolder來解決這個問題呢?
(橋黑板!!只能提高刷新時的效率!!)
熟悉洋神的朋友一定看過這篇文章。 Android 快速開發系列 打造萬能的ListView GridView 適配器
這裡我們引入的NestFullViewHolder 就是這種思想的ViewHolder,
public class NestFullViewHolder {
private SparseArray mViews;
private View mConvertView;
private Context mContext;
public NestFullViewHolder(Context context, View view) {
mContext = context;
this.mViews = new SparseArray();
mConvertView = view;
}
/**
* 通過viewId獲取控件
*
* @param viewId
* @return
*/
public T getView(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
public View getConvertView() {
return mConvertView;
}
public NestFullViewHolder setSelected(int viewId, boolean flag) {
View v = getView(viewId);
v.setSelected(flag);
return this;
}
/**
* 設置TextView的值
*
* @param viewId
* @param text
* @return
*/
public NestFullViewHolder setText(int viewId, String text) {
TextView tv = getView(viewId);
tv.setText(text);
return this;
}
............省略大量常用set方法代碼
建議看洋神文章詳解,不過我這裡也會簡單講解一下:
構造方法裡NestFullViewHolder(Context context, View view)傳入itemView,
利用
private SparseArray
通過
public
該方法是先從mViews的緩存裡尋找View,如果找到了直接返回,
如果沒找到就view = mConvertView.findViewById(viewId);執行findViewById,得到這個View,並放入mViews的緩存裡,這樣下次就不用執行findViewById方法。
並封裝一些常用的方法,例如setText、setImageResource等。。。
有了這個NestFullViewHolder,我們如下改寫NestFullListView:
public class NestFullListView extends LinearLayout {
private List mVHCahces;//緩存ViewHolder,按照add的順序緩存,
//.....無關和成長期重復代碼
private void init(Context context) {
mVHCahces = new ArrayList();
}
//.....無關和成長期重復代碼
public void updateUI() {
if (null != mAdapter) {
if (null != mAdapter.getDatas() && !mAdapter.getDatas().isEmpty()) {
//數據源有數據
if (mAdapter.getDatas().size() > getChildCount()) {//數據源大於現有子View不清空
} else if (mAdapter.getDatas().size() < getChildCount()) {//數據源小於現有子View,刪除後面多的
removeViews(mAdapter.getDatas().size(), getChildCount() - mAdapter.getDatas().size());
//刪除View也清緩存
while (mVHCahces.size() > mAdapter.getDatas().size()) {
mVHCahces.remove(mVHCahces.size() - 1);
}
}
for (int i = 0; i < mAdapter.getDatas().size(); i++) {
NestFullViewHolder holder;
if (mVHCahces.size() - 1 >= i) {//說明有緩存,不用inflate,否則inflate
holder = mVHCahces.get(i);
} else {
holder = new NestFullViewHolder(getContext(), mInflater.inflate(mAdapter.getItemLayoutId(), this, false));
mVHCahces.add(holder);//inflate 出來後 add進來緩存
}
mAdapter.onBind(i, holder);
//如果View沒有父控件 添加
if (null == holder.getConvertView().getParent()) {
this.addView(holder.getConvertView());
}
}
} else {
removeAllViews();//數據源沒數據 清空視圖
}
} else {
removeAllViews();//適配器為空 清空視圖
}
}
}
代碼和成長期基本一致,
只是將緩存itemView的mViewCahces,換成了緩存itemView的ViewHolder的mVHCahces。
將以前增刪mViewCahces的代碼,換成增刪mVHCahces的代碼,
以前回調Adapter的onBind()方法時,給的是ItemView,現在給的是ItemViewHolder。
mAdapter.onBind(i, holder);
所以我們的Adapter也要對應改寫兩個onBind方法:
/**
* 被FullListView調用
*
* @param i
* @param holder
*/
public void onBind(int i, NestFullViewHolder holder) {
//回調bind方法,多傳一個data過去
onBind(i, mDatas.get(i), holder);
}
/**
* 數據綁定方法
*
* @param pos 位置
* @param t 數據
* @param holder ItemView的ViewHolder
*/
public abstract void onBind(int pos, T t, NestFullViewHolder holder);
使用:
nestFullListView = (NestFullListView) findViewById(R.id.cstFullShowListView);
nestFullListView.setAdapter(new NestFullListViewAdapter(R.layout.item_lv, mDatas) {
@Override
public void onBind(int pos, TestBean testBean, NestFullViewHolder holder) {
holder.setText(R.id.tv, testBean.getName());
}
});
對比2成長期的使用,代碼更簡潔了,setText只要一句話,傳入viewId,和value即可。
五 NestFullListView嵌套NestFullListView的onBind()執行次數截圖:
嵌套兩層時,使用方法:
nestFullListView = (NestFullListView) findViewById(R.id.cstFullShowListView);
nestFullListView.setAdapter(new NestFullListViewAdapter(R.layout.item_lv, mDatas) {
@Override
public void onBind(int pos, TestBean testBean, NestFullViewHolder holder) {
Log.d(TAG, "嵌套第一層ScrollView onBind() called with: pos = [" + pos + "], testBean = [" + testBean + "], v = [" + holder + "]");
holder.setText(R.id.tv, testBean.getName());
Glide.with(MainActivity.this).load(testBean.getUrl()).into((ImageView) holder.getView(R.id.iv));
((NestFullListView) holder.getView(R.id.cstFullShowListView2)).setAdapter(new NestFullListViewAdapter(R.layout.item_nest_lv, testBean.getNest()) {
@Override
public void onBind(int pos, NestBean nestBean, NestFullViewHolder holder) {
Log.d(TAG, "嵌套第二層onBind() called with: pos = [" + pos + "], nestBean = [" + nestBean + "], v = [" + holder + "]");
Glide.with(MainActivity.this) .load(nestBean.getUrl()).into((ImageView) holder.getView(R.id.nestIv));
}
});
}
});
除了使用方法不同,其他代碼和第三節 李菊福裡完全一致。可下載文末代碼觀看。
onBind方法次數截圖:
兩層NestFullListView,數據源加起來一共15個Item,那咱就onBind()15次,一次都不多,比隔壁ListViewForScrollView老實多了。
add,delete時,相當於ListView的notifydatasetchanged。onBind()執行次數依然規規矩矩~可下載項目驗證。
六 總結:
其實這種方法,真的稱不上優雅,只不過跟別的方法比起來,相對優雅吧。
在我心中最好的方法就是利用RecyclerView的ItemViewType來解決,可惜由於接口,以及數據結構限制,只能退而求其次。如若有朋友有更好的辦法,歡迎交流。
再次強調一遍~本文的方法只是盡可能的節省刷新時的性能消耗,
不再每次都無腦removeAllViews(),inflate(),addView()。
利用通用的ViewHolder,減少刷新時的findViewById()操作。
不管是ListViewForScrollView 還是本文的NestFullListView,
它們都是在一開始,就把所有的子View統統inflate add bind好了,
不像ListView,RecyclerView..兄弟們,是子View在屏幕上可見時才創建,添加,數據綁定。
github傳送門:
https://github.com/mcxtzhang/NestFullListView
復制FullListView包下三個文件(NestFullListView NestFullListViewAdapter NestFullViewHolder)即可暢快使用,
歡迎討論交流,拍板磚,如有更優方法,真心求指教。
一.概述平時做項目跟使用第三方類庫的時候經常會用到工廠模式.什麼是工廠模式,簡單來說就是他的字面意思.給外部批量提供相同或者不同的產品,而外部不需要關心工廠是如何創建一個
Android 顏色處理(八) SweepGradient 掃描/梯度渲染為什麼什麼叫掃描渲染呢? 相信大家都看過雷達掃描的效果,尤其是在安全軟件中. &nbs
最近項目中需要客戶端往服務器傳輸圖片,並且還需要附帶一些普通參數,研究了幾天,把結果記錄下。 首先客戶端可服務端進行通信一般都是有http請求來發送和接收數據,這裡an
1、對話框通知(Dialog Notification)當你的應用需要顯示一個進度條或需要用戶對信息進行確認時,可以使用對話框來完成。下面代碼將打開一個如圖所示的對話框: