關於ListView的getItemViewType()這個方法的踩坑敘述

有時候我們需要做一個類似下圖的列表,如下,每隔幾個item就需要一個標籤來區分不同的數據類型,這個時候就需要用到getItemViewType()來做區分了

BaseAdapter中有2個方法:

1.getItemViewType(int position);//得到當前item的類型

2.getViewTypeCount()//得到不同的item的總數,下面圖上的類型是2種

//下面貼一段代碼(因爲完整的項目需要關係很多代碼,所以只貼Adapter的代碼)

package com.yy.ent.mobile.ui.live.livelist;

import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.yy.ent.cherry.Cherry;
import com.yy.ent.cherry.ext.image.CircleImageView;
import com.yy.ent.cherry.ext.image.ImageConfig;
import com.yy.ent.cherry.ext.image.ImageManager;
import com.yy.ent.cherry.ext.image.RecycleImageView;
import com.yy.ent.mobile.entity.livelist.Lives;
import com.yy.ent.mobile.ui.base.XBaseAdapter;
import com.yy.ent.mobile.ui.live.widget.RayRelative;
import com.yy.ent.mobile.ui.personal.OthersActivity;
import com.yy.ent.mobile.ui.util.NavigationUtils;
import com.yy.ent.mobile.ui.util.StringUtils;
import com.yy.ent.mobile.ui.util.ViewHelper;
import com.yy.ent.show.ui.R;

 
public class ShowLiveAdapter extends XBaseAdapter<Lives> {
    public static final String MOBILE_LIVE_ITEM_CLICK = "mobile_live_item_click";
    private static final int VIEW_COUNT = 2;

    public static final int LIVE_HEADER = 0;
    public static final int LIVE_CONTENT = 1;

    public static final int HEADER_TAG = 110;
    private String TAG = ShowLiveAdapter.class.getSimpleName();

    public ShowLiveAdapter(Context context, int resource) {
        super(context, resource);
    }

    @Override
    public int getItemViewType(int position) {
        if (list.get(position).status == HEADER_TAG) {
            return LIVE_HEADER;
        } else {
            return LIVE_CONTENT;
        }
    }

    @Override
    public int getViewTypeCount() {
        return VIEW_COUNT;
    }

    @Override
    public View getYView(int i, View itemView, ViewGroup viewGroup) {
        ViewHolder holder = null;
        int type = getItemViewType(i);
        if (itemView == null) {
            switch (type) {
                case LIVE_HEADER:
                    TextView hot = new TextView(context);
                    hot.setText("熱門直播:");
                    hot.setTextColor(context.getResources().getColor(R.color.link_color));
//                    if (list.size() <= 1) {
//                        hot.setVisibility(View.GONE);
//                    }else {
//                        hot.setVisibility(View.VISIBLE);
//                    }
                    itemView = hot;
                    break;
                case LIVE_CONTENT:
                    itemView = listContainer.inflate(itemViewResource, null);
                    holder = new ViewHolder();
                    holder.anchorNick = (TextView) itemView.findViewById(R.id.live_author);
                    holder.liveTitle = (TextView) itemView.findViewById(R.id.live_title);
                    holder.liveAddress = (TextView) itemView.findViewById(R.id.live_address);
                    holder.head = (CircleImageView) itemView.findViewById(R.id.live_head);
                    holder.liveGuset = (TextView) itemView.findViewById(R.id.live_guest_num);
                    holder.jobTag = (LinearLayout) itemView.findViewById(R.id.live_job_tag);
                    holder.liveCover = (RecycleImageView) itemView.findViewById(R.id.live_work_cover);
                    holder.cutTime = (TextView) itemView.findViewById(R.id.live_cut_time);
                    holder.liveProgress = (RayRelative) itemView.findViewById(R.id.live_time_progress);
                    itemView.setTag(holder);
                    break;
            }

        } else {
            if (type == LIVE_CONTENT) {
                holder = (ViewHolder) itemView.getTag();
            }
        }
        if (type == LIVE_CONTENT) {
            Lives lives = list.get(i);
            holder.cutTime.setText(StringUtils.formatTime(lives.countdown));
            holder.liveGuset.setText(lives.online + "人在觀看");
            holder.anchorNick.setText(lives.anchorNick);
            holder.liveTitle.setText(lives.title);
            holder.liveAddress.setText(lives.location);
            ImageManager.instance().loadImage(lives.anchorAvatar, holder.head, ImageConfig.defaultImageConfig(), R.drawable.video_default_cover);
            ImageManager.instance().loadImage(lives.coverUri, holder.liveCover, getWidth(), 300, R.drawable.video_default_cover);
            if (holder.jobTag.getChildCount() > 0) {
                holder.jobTag.removeAllViews();
            }
            for (int j = 0; j < lives.liveTag.length; j++) {
                TextView tv = (TextView) listContainer.inflate(R.layout.layout_label,
                        holder.jobTag, false);
                tv.setText(lives.liveTag[j]);
                holder.jobTag.addView(tv);
            }
            holder.liveProgress.setProgress(getProgress(lives.liveDuration, lives.countdown));
            ItemOnClick onClick = new ItemOnClick(lives);
            holder.liveCover.setOnClickListener(onClick);
            holder.head.setOnClickListener(onClick);
        }


        return itemView;
    }

    private int getProgress(long liveDuration, long countdown) {
        float rate = (float)countdown/ liveDuration ;
        int width = getWidth();
        int progress = (int) (width * rate);
        return progress;
    }

    public int getWidth() {
        return ViewHelper.getDisplayMetrics(context).widthPixels;
    }

    class ItemOnClick implements View.OnClickListener {
        private Lives item;

        ItemOnClick(Lives item) {
            this.item = item;
        }

        @Override
        public void onClick(View view) {
            if (view.getId() == R.id.live_work_cover) {
                if (StringUtils.canClick()) {
                    if (item.status == 0) {
                        Cherry.notityUI(MOBILE_LIVE_ITEM_CLICK, item);
                    } else {
                        NavigationUtils.startToLiveReviewActivity(context, item
                        );
                    }
                }

            }

            if (view.getId() == R.id.live_head) {
                Intent intent = new Intent(context, OthersActivity.class);
                intent.putExtra("personal_activity_arg_uid", item.anchorId + "");
                context.startActivity(intent);
            }
        }
    }

    class ViewHolder {
        public RayRelative liveProgress;
        public TextView liveTitle;
        public TextView liveAddress;
        public CircleImageView head;
        public RecycleImageView liveCover;
        public LinearLayout jobTag;
        public TextView liveGuset;
        public TextView cutTime;
        public TextView anchorNick;
    }
}

如上,有2個常量參數LIVE_HEADER和LIVE_CONTENT,它們分別代表了2種item的類型,再看看它們的值分別是0和1,注意:這裏的值必須是0和1,不能大於1,也不能爲其他的數字,如2,3,0x1等等,否則你在getItemViewType()的時候就會報java.lang.ArrayIndexOutOfBoundsException這個異常,因爲你的getViewTypeCount返回的數量是2,所以只能有2種類型,也就是0或者1,如果你的類型設置爲2或者3,就會超出了類型數量, 至於具體原因就要看看android系統的源碼了.


如果出現異常,這個時候報的異常是這樣的(只貼了主要的異常部分):

java.lang.ArrayIndexOutOfBoundsException: length=2; index=10
            at android.widget.AbsListView$RecycleBin.addScrapView(AbsListView.java:6792)
            at android.widget.ListView.measureHeightOfChildren(ListView.java:1275)
            at android.widget.ListView.onMeasure(ListView.java:1175)
            at com.yy.ent.mobile.ui.widget.FixListView.onMeasure(FixListView.java:35)
然後依次定位最下面的35行,到super.onMeasure(widthMeasureSpec, expandSpec);這行代碼,也就是測量item的大小時異常

然後是ListView內部的onMeasure()方法:

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Sets up mListPadding
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int childWidth = 0;
        int childHeight = 0;
        int childState = 0;

        mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
        if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
                heightMode == MeasureSpec.UNSPECIFIED)) {
            final View child = obtainView(0, mIsScrap);

            measureScrapChild(child, 0, widthMeasureSpec);

            childWidth = child.getMeasuredWidth();
            childHeight = child.getMeasuredHeight();
            childState = combineMeasuredStates(childState, child.getMeasuredState());

            if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
                    ((LayoutParams) child.getLayoutParams()).viewType)) {
                mRecycler.addScrapView(child, 0);
            }
        }

        if (widthMode == MeasureSpec.UNSPECIFIED) {
            widthSize = mListPadding.left + mListPadding.right + childWidth +
                    getVerticalScrollbarWidth();
        } else {
            widthSize |= (childState&MEASURED_STATE_MASK);
        }

        if (heightMode == MeasureSpec.UNSPECIFIED) {
            heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                    getVerticalFadingEdgeLength() * 2;
        }

        if (heightMode == MeasureSpec.AT_MOST) {
            // TODO: after first layout we should maybe start at the first visible position, not 0
            heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
        }

        setMeasuredDimension(widthSize , heightSize);
        mWidthMeasureSpec = widthMeasureSpec;        
    }
這裏計算listview的沒一個item的高度,寬度,請看最下面的倒數第3行代碼:measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);//測量子控件的寬高,然後經過一系列的方法及接口調用(這裏樓主偷懶,代碼太多啦)就會走到AbsListView的getScrapView(int position) 這個方法,這裏就是最終得到的item的view類型.

到這個方法裏面去

 /**
         * @return A view from the ScrapViews collection. These are unordered.
         */
        View getScrapView(int position) {
            if (mViewTypeCount == 1) {
                return retrieveFromScrap(mCurrentScrap, position);
            } else {
                final int whichScrap = mAdapter.getItemViewType(position);
                if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
                    return retrieveFromScrap(mScrapViews[whichScrap], position);
                }
            }
            return null;
        }

看到mAdapter.getItemViewType(position)這行代碼了嗎,其中的position就是你傳入的item的下標,返回值就是你之前定義的類型0,1,再看看下面的if判斷是不是就一目瞭然了,其實mScrapViews.length=getViewTypeCount(),所以你這裏如果傳入是3,4的話,if條件就不會成立了,view也就無法進行繪製,mScrapViews是一個ArrayList,取到的值大於getViewTypeCount(),那麼就會造成數組越界了.

        因爲憑自己的感覺去定義了ItemViewType的類型值,所以列表一直報錯,這裏爲了防止其他和我一樣踩坑的人不知所云,特意記錄下來,後面的看不懂沒關係,只要注意前面紅色標記的位置就可以了



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章