ListView作为传统展示大量数据的基本控件,其回收能力是核心。考虑到数据并不一定是单一的样式,因此,viewTpe使得多样式的列表结构变得简单清晰。
效果图:
代码:
代码很简单,主要就是在adapter里面重写
getViewTypeCount()
getItemViewType(int position)
这两个方法。
MultiStyleListAdapter.java:
public class MultiStyleListAdapter extends AbstractListAdapter<Object> {
private Class[] dataClasses;
@Override
public View getView(int position, View view, ViewGroup parent) {
int viewType=getItemViewType(position);
if (viewType==0) {
view = getStyle1View(position, view, parent);
}else if (viewType==1) {
view = getStyle2View(position, view, parent);
} else if(viewType==2){
view = getStyle3View(position, view, parent);
}else if(viewType==3){
view=getStyle4View(position, view, parent);
}
return view;
}
public MultiStyleListAdapter(Context context) {
// TODO Auto-generated constructor stub
super(context);
dataClasses=new Class[]{Data1.class,Data2.class,Data3.class,Data4.class};
}
/**
* 注意返回的类型:
* 如果只有1种布局类型,那么返回的type是0;
* 如果2种类型,必须是0,1
* 如果3种类型,必须是0,1,2
* 。。。。
* 依次类推
* @param position 根据position返回对应位置的视图类型
* @return
*/
@Override
public int getItemViewType(int position) {
// TODO Auto-generated method stub
Object object=getItem(position);
for(int i=0,size=dataClasses.length;i<size;i++){
if(object.getClass()== dataClasses[i]){
return i;
}
}
return 0;
}
/**
* @return 返回值是,布局种类总数
*/
@Override
public int getViewTypeCount() {
// TODO Auto-generated method stub
return dataClasses.length;
}
private View getStyle1View(final int position, View convertView,
ViewGroup parent) {
final Styly1ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item_style_1, null);
holder = new Styly1ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (Styly1ViewHolder) convertView.getTag();
}
final Data1 item=(Data1)mList.get(position);
holder.tvIndex.setText("index="+position);
holder.content.setText(item.content);
return convertView;
}
private View getStyle2View(final int position, View convertView,
ViewGroup parent) {
final Styly2ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item_style_2, null);
holder = new Styly2ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (Styly2ViewHolder) convertView.getTag();
}
final Data2 item=(Data2)mList.get(position);
holder.tvIndex.setText("index="+position);
holder.pic.setImageResource(item.img);
return convertView;
}
private View getStyle3View(final int position, View convertView,
ViewGroup parent) {
final Styly3ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item_style_3, null);
holder = new Styly3ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (Styly3ViewHolder) convertView.getTag();
}
final Data3 item=(Data3)mList.get(position);
holder.tvIndex.setText("index="+position);
holder.pic1.setImageResource(item.img1);
holder.pic2.setImageResource(item.img2);
holder.pic3.setImageResource(item.img3);
return convertView;
}
private View getStyle4View(final int position, View convertView,
ViewGroup parent) {
final Styly4ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item_style_4, null);
holder = new Styly4ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (Styly4ViewHolder) convertView.getTag();
}
final Data4 item=(Data4)mList.get(position);
holder.tvIndex.setText("index="+position);
holder.checkBox.setText(item.content);
holder.checkBox.setChecked(item.isChecked);
return convertView;
}
class Styly1ViewHolder {
TextView tvIndex;
TextView content;
public Styly1ViewHolder(View root){
content=(TextView)root.findViewById(R.id.content);
tvIndex=(TextView)root.findViewById(R.id.index);
}
}
class Styly2ViewHolder {
TextView tvIndex;
ImageView pic;
public Styly2ViewHolder(View root){
pic=(ImageView)root.findViewById(R.id.content);
tvIndex=(TextView)root.findViewById(R.id.index);
}
}
class Styly3ViewHolder {
TextView tvIndex;
ImageView pic1;
ImageView pic2;
ImageView pic3;
public Styly3ViewHolder(View root){
pic1=(ImageView)root.findViewById(R.id.img1);
pic2=(ImageView)root.findViewById(R.id.img2);
pic3=(ImageView)root.findViewById(R.id.img3);
tvIndex=(TextView)root.findViewById(R.id.index);
}
}
class Styly4ViewHolder {
TextView tvIndex;
CheckBox checkBox;
public Styly4ViewHolder(View root){
checkBox=(CheckBox)root.findViewById(R.id.checkbox);
tvIndex=(TextView)root.findViewById(R.id.index);
}
}
}
demo用了4个样式,分别对应4个viewholder,定义4个数据模型,data1,data2,data3,data4,根据viewtype来实例化或复用对应view,并绑定对应数据。
注意getViewType返回值,必须从0开始,依次增大。否则要抛ArrayIndexOutOfBoundsException异常,原因文章后面分析。
AbstractListAdapter.java 继承至BaseAdapter,只是对它进行部分重写和封装,以及使用泛型后的一个基类。
public abstract class AbstractListAdapter <T> extends BaseAdapter {
protected List<T> mList;
protected Context mContext;
private AdapterView mListView;
protected LayoutInflater mInflater;
public AbstractListAdapter(Activity context) {
this.mContext = context;
mInflater = LayoutInflater.from(context);
}
public AbstractListAdapter(Context context) {
mContext=context;
mInflater = LayoutInflater.from(context);
}
@Override
public Object getItem(int i) {
return mList == null ? null : mList.get(i);
}
@Override
public int getCount() {
return mList == null ? 0 : mList.size();
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public abstract View getView(int i, View view, ViewGroup viewGroup);
public void setList(List<T> list) {
this.mList = list;
}
public List<T> getList() {
return this.mList;
}
public AdapterView getListView(){
return mListView;
}
public void setListView(AdapterView listView){
mListView = listView;
}
public Context getContext(){
return mContext;
}
public void setList(T[] list){
if (list == null) return;
ArrayList<T> arrayList = new ArrayList<T>(list.length);
for (T t : list) {
arrayList.add(t);
}
setList(arrayList);
}
public void clear() {
if (mList != null && mList.size() > 0){
mList.clear();
}
}
}
然后添加示例数据:
MainActivity.java
public class MainActivity extends AppCompatActivity {
private ListView mListview;
private MultiStyleListAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListview=(ListView)findViewById(R.id.listview);
mAdapter=new MultiStyleListAdapter(this);
mListview.setAdapter(mAdapter);
createData();
}
private void createData(){
List<Object> dataAll=new ArrayList<>();
dataAll.add(new Data1("小白兔和大灰狼的故事"));
dataAll.add(new Data1("话说小白兔遇到大灰狼"));
dataAll.add(new Data2(R.drawable.img1));
dataAll.add(new Data2(R.drawable.img2));
dataAll.add(new Data1("小白兔说大灰狼大灰狼 你快问我是不是小白兔"));
dataAll.add(new Data3(R.drawable.img3,R.drawable.img4,R.drawable.img5));
dataAll.add(new Data4("你快问啊快问啊!!!!",true));
dataAll.add(new Data1("大灰狼说 你是不是小白兔啊?"));
dataAll.add(new Data2(R.drawable.img6));
dataAll.add(new Data2(R.drawable.img7));
dataAll.add(new Data1("小白兔很高兴 是的是的我是的!!!"));
dataAll.add(new Data1("然后"));
dataAll.add(new Data1("小白兔又说 大灰狼大灰狼 你快问我是不是长颈鹿"));
dataAll.add(new Data2(R.drawable.img8));
dataAll.add(new Data1("你快问啊快问啊!!!!"));
dataAll.add(new Data1("大灰狼很无奈 好吧。。。那。。。你是不是长颈鹿啊"));
dataAll.add(new Data4("小白兔朝他后脑勺一巴掌 你个笨蛋!我都说了我是小白兔了!!!。。。。。",true));
dataAll.add(new Data4("撒角度看",true));
dataAll.add(new Data3(R.drawable.img9,R.drawable.img10,R.drawable.img11));
dataAll.add(new Data1("。。。。。。。。。。。。"));
mAdapter.setList(dataAll);
mAdapter.notifyDataSetChanged();
}
}
viewType分析
好了,代码贴完,可以开始分析了。
大家都知道ListView,GridView都是继承至AbsListView。它的回收能力来自于abslistview。
滚动时,超出屏幕的视图会被扔进回收站,当ListView判定出即将有item进入屏幕时,又会从回收站里面把视图拿出来重用,当然,前提是回收站里有满足要求的视图。否则会创建新实例。这部分源码不是重点,有兴趣的同学自行查看源码或相关文档。
RecycleBin,这就是这个回收站,在ListView实例创建时而被创建。其中有几个比较重要的成员变量
View[] mActiveViews 存的是处于屏幕里的view
ArrayList<View>[] mScrapViews; 存的是所有废弃的view,它就是我们要说的重点。这个数组的每个元素都是一个view的集合,其实也就是每个类型一个集合。
RecycleBin.setViewTypeCount(int viewTypeCount)是在ListView的setAdapter里面被调用的,这时就会根据type的数量创建对应个数的view集合。
getScrapView()里以viewType取集合,所以,前面所说的adapter里面的getviewtype的返回值必须从0开始,且不间断的自然数。
class RecycleBin {
//根据viewtype的种类数量创建arraylist的数组
public void setViewTypeCount(int viewTypeCount) {
if (viewTypeCount < 1) {
throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
}
//noinspection unchecked
ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
for (int i = 0; i < viewTypeCount; i++) {
scrapViews[i] = new ArrayList<View>();
}
mViewTypeCount = viewTypeCount;
mCurrentScrap = scrapViews[0];
mScrapViews = scrapViews;
}
//从垃圾堆里重新取得view
private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
final int size = scrapViews.size();
if (size > 0) {
// See if we still have a view for this position or ID.
for (int i = 0; i < size; i++) {
final View view = scrapViews.get(i);
final AbsListView.LayoutParams params =
(AbsListView.LayoutParams) view.getLayoutParams();
if (mAdapterHasStableIds) {
final long id = mAdapter.getItemId(position);
if (id == params.itemId) {
return scrapViews.remove(i);
}
} else if (params.scrappedFromPosition == position) {
final View scrap = scrapViews.remove(i);
clearAccessibilityFromScrap(scrap);
return scrap;
}
}
final View scrap = scrapViews.remove(size - 1);
clearAccessibilityFromScrap(scrap);
return scrap;
} else {
return null;
}
}
//根据viewtype获取对应类型的view集合
View getScrapView(int position) {
final int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap < 0) {
return null;
}
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
} else if (whichScrap < mScrapViews.length) {
//以viewtype为下标取集合
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
return null;
}
}
AbsListView.java:
//listview里的每个视图就是在这个方法中被创建的
View obtainView(int position, boolean[] isScrap){
......
//从回收站重新取出对应type的view
final View scrapView = mRecycler.getScrapView(position);
//再传入getview里重新绑定数据。
final View child = mAdapter.getView(position, scrapView, this);
......
}
AbsListView的obtainview方法创建或者复用回收站里面的view
absListView的trackMotionScroll()--->mRecycler.addScrapView(child, position);
trackMotionScroll()滚动时会被调用,这里面判断哪些view被废弃。
所以ListView滚动时,判定哪些view超出屏幕,超出就被放入回收站里,等又需要view的时候,listview根据viewType类型从回收站里取出来复用,并回调到adapter的getView()方法里面,从而达到多类型复用的效果。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。