Android之ListView的getItemViewType和getViewTypeCount

PS:感觉这两个方法其实还是很容易理解的,也算是给我其他两个朋友写的吧,帮他们搞清楚这两个方法的用法和概念。同时还有一些小细节问题需要注意。

学习内容:

1.getItemViewType和getViewTypeCount

getItemViewType和getViewTypeCount是ListView中实现复杂列表的两个相关的方法,普通的ListView中Item是相同的,那么我们只需要实现Adapter中四个抽象方法即可,但是如果页面中Item长得比较的复杂呢?比如说这个。

Android之ListView的getItemViewType和getViewTypeCount

比如说这个列表项,其实也不是很复杂,这种类型的Item也有其他的实现方式,比如说在Adapter中实现SectionIndexer也是可以实现的,但是我们就拿这个来说明一下问题,如果一个Item第一种类型是TextView,第二种类型是ImageView+Button+TextView呢,那么这样复杂的列表我们就需要使用getItemViewType和getTypeViewCount两个方法去实现了。这两个方法理解起来还是比较容易的,获取Item中Type的类型以及Item中Type的相关数量。废话就不多说了,直接说实现方式。

public class ListAdapter extends BaseAdapter {

    /**
     * Item类型,int值.必须从0开始依次递增.
     * */
    private static final int TYPE_TITLE = 0;
    private static final int TYPE_CONTENT = 1;

    /**
     * Item Type 的数量
     * */
    private static final int TYPE_ITEM_COUNT = 2;

    /**
     * 数据
     * */
    private List<Company> mData = new ArrayList<>;
    private Context context;



    public ListAdapter(Context context,List<Company>mData){
        this.context = context;
        this.mData = mData;
    }


    @Override
    public int getCount {
        return mData.size;
    }

    @Override
    public Object getItem(int position) {
        return mData.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup viewGroup) {

        /**
         * 不同类型的ViewHolder
         * */
        TitleViewHolder titleViewHolder = null;
        CompanyViewHolder contentViewHolder = null;
        /**
         * 对类型进行判断,分别inflate不同的布局.
         * */
        switch (getItemViewType(position)){
 case TYPE_TITLE:
 titleViewHolder = new TitleViewHolder;
 if(convertView == null){
 convertView = View.inflate(context, R.layout.view_holder_company_index,null);
 titleViewHolder.title = (TextView) convertView.findViewById(R.id.tv_title);
 //setTag
 convertView.setTag(titleViewHolder);
 }else{
 //getTag;
 titleViewHolder = (TitleViewHolder) convertView.getTag;
 }
 titleViewHolder.title.setText(mData.get(position).getName);
 break;
 case TYPE_CONTENT:
 contentViewHolder = new CompanyViewHolder;
 if(convertView == null){
 convertView = View.inflate(context,R.layout.view_holder_company,null);
 contentViewHolder.content = (TextView) convertView.findViewById(R.id.tv_content);
 convertView.setTag(contentViewHolder);
 }else{
 contentViewHolder = (CompanyViewHolder) convertView.getTag;
 }
 contentViewHolder.content.setText(mData.get(position).getCode);
 break;
        }
        return convertView;
    }

    /**
     * 根据position获取Item的类型
     * */
    @Override
    public int getItemViewType(int position) {
        if(TextUtils.isEmpty(mData.get(position).getCode)){
 return TYPE_TITLE;
        }else{
 return TYPE_CONTENT;
        }
    }

    /**
     * 返回Item Type的总数量
     * */
    @Override
    public int getViewTypeCount {
        return TYPE_ITEM_COUNT;
    }

    static class TitleViewHolder{
        TextView title;
    }

    static class CompanyViewHolder{
        TextView content;
    }
}
  • 首先我们需要为不同的Item设置不同的数值,int值,因为getItemViewType返回的是int值,所以需定义成int,必须从0开始,依次递增。原因我后续会做出解释。
  • 重写getItemViewType和getViewTypeCount方法,getViewTypeCount返回Item的类型总数,getViewTypeCount则需要进行判断,判断方式一般都是通过JavaBean中的相关字段来判断的,因此这块不需要过于纠结。只需要根据position获取Item的具体类型进行判断然后就返回就可以了。
  • 定义ViewHolder,根据类型的不同需要定义多个ViewHolder,减少findViewById的次数。
  • 重写getView中的相关方法,在getView中首先根据position获取Item的类型去加载不用的布局,这里同时会setViewType为不同类型的Item设置RecycleBin,解决ListView由于多个类型Item的复用问题。不清楚RecycleBin机制的读者可以去看下ListView的复用机制这里说到了RecycleBin,如果不懂这个机制是看不明白下面的解释的。
  • 最后根据传递过来的数据setAdapter然后为Item进行赋值就完成了。

大体的一个思路就是这样实现的,这里需要说一下为什么定义Item的类型的时候必须要从0开始,依次递增,那么原因是什么呢?如果我们有三种类型,我们将Item定义成1,2,4,那么势必会出现ArrayIndexOutOfBoundsException,也就是所谓的数组越界,我上网查了很多资料都说会出现异常,并且Google也确实标明了,Note: Integers must be in the range 0 togetViewTypeCount- 1.IGNORE_ITEM_VIEW_TYPEcan also be returned.但是看到这里就没有后续了,没人会去说明这个问题是怎样发生的,为什么要这样去定义。可能我就是闲的蛋疼的那种人,不弄明白确实感到不舒服。我在解释一下具体的原因:

其实发生这种情况一般都是我们在下拉的时候出现的问题,在第一次加载第一页的时候是不会直接出现崩溃现象的,那么心细的读者可能会明白这有可能是ListView在复用时出现的问题,其实却是就是ListView复用机制导致的。我们来看一下这个方法:

/**
 *ListView在针对不同Item复用时会调用这个方法
 *为每一种不同的Item设置一个RecycleBin,用于复用.
 */
public void setViewTypeCount(int viewTypeCount) {
   
    if (viewTypeCount < 1) {
        throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
    }
    // noinspection unchecked  
    /**
     * 根据viewTypeCount的数量设置一个ArrayList.
     * 同时为每一个Item再设置一个ArrayList,用来存储ScrapView.
     * 相当于一个二维数组来维护每一个Item的ScrapView数组.
     * 这里也就相当于为不同的Item设置单独的RecycleBin.
     */
    ArrayList<View> scrapViews = new ArrayList[viewTypeCount];
    for (int i = 0; i < viewTypeCount; i++) {
       scrapViews[i] = new ArrayList<View>;
    }
    //保存ViewTypeCount,也就 = 2
    mViewTypeCount = viewTypeCount;
    //当前的Scrap是第一个Item的ScrapView数组.
    mCurrentScrap = scrapViews[0];
    //mScrapViews就保存了一个二维数组维护的RecycleBin.
    mScrapViews = scrapViews;
}

Android之ListView的getItemViewType和getViewTypeCount

这是具体的数据结构,简单理解就是每一个Item都有对应的ScrapView数组。这里其实并不是出问题的地方,我们都知道Item一旦被移出了屏幕,首先会Detach掉,然后被加入到mScrapView数组中(废弃View池),那么在addScrapView的时候就会出现异常.

  void addScrapView(View scrap) {  
        AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams;  
        if (lp == null) {  
 return;  
        }  
        // Don't put header or footer views or views that should be ignored  
        // into the scrap heap  
        int viewType = lp.viewType;  
        if (!shouldRecycleViewType(viewType)) {  
 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {  
 removeDetachedView(scrap, false);  
 }  
 return;  
        }  
        /**
         *核心代码就是这块,由于我们mViewTypeCount != 1 的,因此if条件不成立.
         *因此会执行else代码.
         */
        if (mViewTypeCount == 1) {  
 dispatchFinishTemporaryDetach(scrap);  
 mCurrentScrap.add(scrap);  
        } else {  
 dispatchFinishTemporaryDetach(scrap);  
 mScrapViews[viewType].add(scrap);  
        }  
  
        if (mRecyclerListener != null) {  
 mRecyclerListener.onMovedToScrapHeap(scrap);  
        }  
    }  

问题就在于这个else代码当中,我们可以看到mScrapViews[viewType].add(scrap)代码执行了,我们前面图片上显示的,mScrapViews是很据Item的种类数量new出来的,由于我们Item总数是两种类型,那么mScrapViews.length = 2,但是这里是mScrapView[viewtype],viewtype是什么,其实就是我们getItemViewType的返回值,如果我们将类型定义成2和3,那么他会访问mScrapView[2]和mScrapView[3],可想而知,一定会出现ArrayIndexOutOffBoundsException.这就是数组越界的真正原因。

基本就解释完了,贴上一个从GitHub荡下来的源代码:Demo下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Android中使用ListView来显示天气图标和温度,可以按照以下步骤进行操作: 1. 准备数据:首先需要准备一个包含天气图标和温度信息的列表数据,可以使用ArrayList或者其他数据结构来存储。 2. 创建布局:为了显示天气图标和温度,需要为ListView中的每一项创建一个布局,可以使用LinearLayout或者RelativeLayout等布局来设计每一项的布局。 3. 创建适配器:创建一个继承自BaseAdapter的适配器类,实现getView()方法来设置每一项的布局和数据。 4. 设置适配器:在Activity或者Fragment中获取ListView控件,然后设置适配器即可显示列表数据。 以下是一个简单的示例代码,实现了一个包含天气图标和温度的列表: ``` public class MyAdapter extends BaseAdapter { private Context mContext; private ArrayList<WeatherData> mData; public MyAdapter(Context context, ArrayList<WeatherData> data) { mContext = context; mData = data; } @Override public int getCount() { return mData.size(); } @Override public Object getItem(int position) { return mData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { View view = convertView; ViewHolder holder; if (view == null) { view = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false); holder = new ViewHolder(); holder.imageView = view.findViewById(R.id.image_view); holder.textView = view.findViewById(R.id.text_view); view.setTag(holder); } else { holder = (ViewHolder) view.getTag(); } WeatherData weatherData = mData.get(position); holder.textView.setText(weatherData.getTemperature() + "℃"); holder.imageView.setImageResource(weatherData.getImageResId()); return view; } static class ViewHolder { ImageView imageView; TextView textView; } } public class WeatherData { private int mImageResId; private int mTemperature; public WeatherData(int imageResId, int temperature) { mImageResId = imageResId; mTemperature = temperature; } public int getImageResId() { return mImageResId; } public int getTemperature() { return mTemperature; } } // 在Activity或者Fragment中使用适配器来显示ListView ArrayList<WeatherData> data = new ArrayList<>(); data.add(new WeatherData(R.drawable.weather_sunny, 28)); data.add(new WeatherData(R.drawable.weather_cloudy, 24)); data.add(new WeatherData(R.drawable.weather_rainy, 20)); ListView listView = findViewById(R.id.list_view); MyAdapter adapter = new MyAdapter(this, data); listView.setAdapter(adapter); ``` 在上述代码中,我们定义了一个WeatherData类来存储天气图标和温度信息,然后我们创建了一个MyAdapter类来实现适配器,通过重写getView()方法设置每一项的布局和数据。最后在Activity或者Fragment中使用适配器来显示ListView
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值