最近有一個列表效果,需要一個列表有多種布局,最終效果如下:
這個我也問了同事以及開發群里的朋友,居然都沒得到最優的實現方式的回答,看來這種復雜列表的需求還是比較少的,我自己也走了一些彎路,把我幾個實現的方式整理下,希望對於還不了解的朋友有所幫助。
實現方式1:(每次getView時重新inflate itemView,convertView沒有復用,性能低,運行沒問題)
private class MyAdapter extends BaseAdapter{ private List<Object> datas = Collections.EMPTY_LIST; public void setDatas(List<Object> datas) { if(datas == null){ datas = Collections.EMPTY_LIST; } this.datas = datas; notifyDataSetChanged(); } @Override public int getCount() { return datas.size(); } @Override public Object getItem(int position) { return datas.get(position); } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { Object data = getItem(position); if(data instanceof Folder){ FolderViewHolder holder = null; if(convertView != null && convertView.getTag() instanceof FolderViewHolder){ //View與數據類型一致 holder = (FolderViewHolder) convertView.getTag(); }else{ convertView = mInflater.inflate(R.layout.listitem1, null); holder = new FolderViewHolder(convertView); convertView.setTag(holder); } holder.setData((Folder)data); }else{ FileViewHolder holder = null; if(convertView != null && convertView.getTag() instanceof FileViewHolder){ //View與數據類型一致 holder = (FileViewHolder) convertView.getTag(); }else{ convertView = mInflater.inflate(R.layout.listitem2, null); holder = new FileViewHolder(convertView); convertView.setTag(holder); } holder.setData((File)data); } return convertView; } } private class FolderViewHolder{ public TextView tvName; public FolderViewHolder(View itemView){ tvName = (TextView) itemView.findViewById(R.id.tvName); } public void setData(Folder data) { tvName.setText(data.name); } } private class FileViewHolder{ public TextView tvName; public FileViewHolder(View itemView){ tvName = (TextView) itemView.findViewById(R.id.tvName); } public void setData(File data) { tvName.setText(data.name); } }
實現方式2:(因為方式1不斷inflate view,影響性能,於是考慮是否能盡可能重用已經inflate的view,於是添加了一個緩存,不過實際測試快速滑動或切換數據會顯示異常,應該是AbsListView#RecycleBin緩存的原因,具體原因我后面理清了再添加,看別人的代碼最痛苦了。。。)
private class MyAdapter extends BaseAdapter{ private List<View> folderViewCaches = new ArrayList<View>(5); private List<View> fileViewCaches = new ArrayList<View>(5); private List<Object> datas = Collections.EMPTY_LIST; public void setDatas(List<Object> datas) { if(datas == null){ datas = Collections.EMPTY_LIST; } this.datas = datas; notifyDataSetChanged(); } @Override public int getCount() { return datas.size(); } @Override public Object getItem(int position) { return datas.get(position); } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { Object data = getItem(position); if(data instanceof Folder){ //文件夾,應該返回R.layout.listitem1對應的View FolderViewHolder holder = null; if(convertView != null && convertView.getTag() instanceof FolderViewHolder){ //View與數據類型一致 holder = (FolderViewHolder) convertView.getTag(); }else{ if(convertView != null){ //緩存到文件列表 fileViewCaches.add(convertView); convertView = null; } //從緩存里面取已從ListView移除的緩存(注釋掉此部分代碼顯示正常) if(!folderViewCaches.isEmpty()){ for(View cache : folderViewCaches){ if(cache.getParent() == null){ //緩存的View已從listView里面移除 convertView = cache; holder = (FolderViewHolder) convertView.getTag(); folderViewCaches.remove(cache); break; } } } //還是沒有,重新inflate if(convertView == null){ convertView = mInflater.inflate(R.layout.listitem1, null); holder = new FolderViewHolder(convertView); convertView.setTag(holder); } } holder.setData((Folder) data); }else{ //文件,應該返回R.layout.listitem2對應的View FileViewHolder holder = null; if(convertView != null && convertView.getTag() instanceof FileViewHolder){ //View與數據類型一致 holder = (FileViewHolder) convertView.getTag(); }else{ if(convertView != null){ //緩存到文件夾列表 folderViewCaches.add(convertView); convertView = null; } //從緩存里面取已從ListView移除的緩存(注釋掉此部分代碼顯示正常) if(!fileViewCaches.isEmpty()){ for(View cache : fileViewCaches){ if(cache.getParent() == null){ //緩存的View已從listView里面移除 convertView = cache; holder = (FileViewHolder) convertView.getTag(); fileViewCaches.remove(cache); break; } } } //還是沒有,重新inflate if(convertView == null){ convertView = mInflater.inflate(R.layout.listitem2, null); holder = new FileViewHolder(convertView); convertView.setTag(holder); } } holder.setData((File) data); } return convertView; } }
實現方式3:(最佳實現,運行正常)
后面仔細閱讀ListView相關源碼,才發現Adapter本身就支持不同的布局了,而且AbsListView#RecycleBin也支持不同類型的布局的緩存策略,RecycleBin.mViewTypeCount標示有多少種View類型。
我們需要做的就是重寫Adapter的下面3個方法:
1.getViewTypeCount:
/** * 有多少種不同布局的View */ @Override public int getViewTypeCount() { return 2; }
2.getItemViewType
/** * 相應position對應的View類型 */ @Override public int getItemViewType(int position) { if(getItem(position) instanceof Folder){ return TYPE_FOLDER; }else{ return TYPE_FILE; } }
3.getView,通過判斷對應position的類型,返回相應類型的view:
@Override public View getView(int position, View convertView, ViewGroup parent) { Object data = getItem(position); if(data instanceof Folder){ //TYPE_FOLDER,文件夾,應該返回R.layout.listitem1對應的View FolderViewHolder holder = null; if(convertView != null){ holder = (FolderViewHolder) convertView.getTag(); }else{ convertView = mInflater.inflate(R.layout.listitem1, null); holder = new FolderViewHolder(convertView); convertView.setTag(holder); } holder.setData((Folder) data); }else{ //TYPE_FILE,文件,應該返回R.layout.listitem2對應的View FileViewHolder holder = null; if(convertView != null){ holder = (FileViewHolder) convertView.getTag(); }else{ convertView = mInflater.inflate(R.layout.listitem2, null); holder = new FileViewHolder(convertView); convertView.setTag(holder); } holder.setData((File) data); } return convertView; }
此demo的github源碼地址:
https://github.com/John-Chen/BlogSamples/tree/master/MultipleListTest
apk下載地址:
https://github.com/John-Chen/BlogSamples/blob/master/MultipleListTest/MultipleListTest.apk
如果寫的有問題的地方,歡迎指教!