ArrayIndexOutOfBoundsException when using ListAdapter
I'm using a custom ListAdapter
for a ListView
. I don't change anything dynamically (view count, view types, enabled/disabled, I read other posts but they changed something from code). I have to do the following to trigger the exception. Scroll down to the bottom of the list and after that scroll up again. At the start of the scroll-up animation I get this exception.
04-14 09:41:43.907: ERROR/AndroidRuntime(400): FATAL EXCEPTION: main
04-14 09:41:43.907: ERROR/AndroidRuntime(400): java.lang.ArrayIndexOutOfBoundsException
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.widget.AbsListView$RecycleBin.addScrapView(AbsListView.java:4540)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.widget.AbsListView.trackMotionScroll(AbsListView.java:3370)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.widget.AbsListView.onTouchEvent(AbsListView.java:2233)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.widget.ListView.onTouchEvent(ListView.java:3446)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.view.View.dispatchTouchEvent(View.java:3885)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:903)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1691)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1125)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.app.Activity.dispatchTouchEvent(Activity.java:2096)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1675)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.view.ViewRoot.deliverPointerEvent(ViewRoot.java:2194)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.view.ViewRoot.handleMessage(ViewRoot.java:1878)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.os.Handler.dispatchMessage(Handler.java:99)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.os.Looper.loop(Looper.java:123)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at android.app.ActivityThread.main(ActivityThread.java:3683)
04-14 09:41:43.907: ERROR/Andr开发者_如何学编程oidRuntime(400): at java.lang.reflect.Method.invokeNative(Native Method)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at java.lang.reflect.Method.invoke(Method.java:507)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
04-14 09:41:43.907: ERROR/AndroidRuntime(400): at dalvik.system.NativeStart.main(Native Method)
Down below is the complete implementation.
public class CompanyDetailsAdapter implements ListAdapter {
private Context context;
private LayoutInflater inflater;
private static final class ViewType {
private static final int HEADER = 1;
private static final int TEXT = 2;
private static final int ADDRESS = 3;
private static final int REVIEWS = 4;
private static final int PRODUCTS = 5;
}
//all the cells in the order they appear
private static final class ViewPositions {
private static final int CompanyNameHeader = 0;
private static final int CompanyName = 1;
private static final int ContactHeader = 2;
private static final int Phone = 3;
private static final int Website = 4;
private static final int AddressHeader = 5;
private static final int Address = 6;
private static final int DescriptionHeader = 7;
private static final int Description = 8;
private static final int ReviewsHeader = 9;
private static final int Reviews = 10;
private static final int ProductsHeader = 11;
private static final int Products = 12;
}
//list of cells by types
private static final int[] viewTypes = {
ViewType.HEADER, //0
ViewType.TEXT, //1
ViewType.HEADER, //2
ViewType.TEXT, //3
ViewType.TEXT, //4
ViewType.HEADER, //5
ViewType.ADDRESS, //6
ViewType.HEADER, //7
ViewType.TEXT, //8
ViewType.HEADER, //9
ViewType.REVIEWS, //10
ViewType.HEADER, //11
ViewType.PRODUCTS //12
};
//headers
private static final int[] headerPositions = {
ViewPositions.CompanyNameHeader,
ViewPositions.ContactHeader,
ViewPositions.AddressHeader,
ViewPositions.DescriptionHeader,
ViewPositions.ReviewsHeader,
ViewPositions.ProductsHeader
};
//header resources for position where needed
private static final int[] headerResources = {
R.string.companyName,
0,
R.string.contact,
0,
0,
R.string.address,
0,
R.string.description,
0,
R.string.reviews,
0,
R.string.products,
0
};
//regular texts
private static final int[] textPositions = {
ViewPositions.CompanyName,
ViewPositions.Phone,
ViewPositions.Website,
ViewPositions.Description,
};
public CompanyDetailsAdapter(Context context) {
this.context = context;
this.inflater = LayoutInflater.from(this.context);
}
public int getCount() {
return viewTypes.length;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return position+1;
}
public int getItemViewType(int position) {
return viewTypes[position];
}
public View getView(int position, View convertView, ViewGroup parent) {
if (arrayContains(headerPositions, position)) {
LinearLayout layout = null;
TextView txtHeader = null;
if (convertView == null) {
layout = (LinearLayout)inflater.inflate(R.layout.header_listview,parent, false);
} else {
layout = (LinearLayout)convertView;
}
txtHeader = (TextView)layout.findViewById(R.id.txtHeader);
String value = context.getText(headerResources[position]).toString();
txtHeader.setText(value);
return layout;
} else if (arrayContains(textPositions, position)) {
TextView txtText = null;
LinearLayout layout = null;
Company c = Company.getSelectedCompany();
if (convertView == null) {
layout = (LinearLayout)inflater.inflate(R.layout.default_text,parent, false);
} else {
layout = (LinearLayout)convertView;
}
txtText = (TextView)layout.findViewById(R.id.txtNormalText);
switch (position) {
case ViewPositions.CompanyName:
txtText.setText(c.getName());
break;
case ViewPositions.Phone:
txtText.setText(c.getPhone());
break;
case ViewPositions.Website:
txtText.setText(c.getWebsiteString());
break;
case ViewPositions.Description:
txtText.setText(c.getDescription());
break;
}
return layout;
} else {
View v = null;
//LinearLayout layout = null;
Company company = Company.getSelectedCompany();
switch (position) {
case ViewPositions.Address:
if (convertView == null)
v = (LinearLayout)inflater.inflate(R.layout.adress, parent,false);
else
v = (LinearLayout)convertView;
TextView txtAddress = (TextView)v.findViewById(R.id.txtAddress);
txtAddress.setText(String.format("%s %s %s", company.getCity(),
company.getStreet(), company.getPostalCode()));
break;
case ViewPositions.Products:
if (convertView == null)
v = (LinearLayout)inflater.inflate(R.layout.products, parent,false);
else
v = (LinearLayout)convertView;
v = (LinearLayout)inflater.inflate(R.layout.products, parent,false);
TextView txtNumProducts = (TextView)v.findViewById(R.id.txtNumProducts);
txtNumProducts.setText(String.valueOf(company.getNrProducts()));
break;
case ViewPositions.Reviews:
if (convertView == null)
v = (LinearLayout)inflater.inflate(R.layout.reviews, parent,false);
else
v = (LinearLayout)convertView;
v = (LinearLayout)inflater.inflate(R.layout.reviews, parent,false);
TextView txtNumReviews = (TextView)v.findViewById(R.id.txtNumReviews);
txtNumReviews.setText(String.valueOf(company.getNrReviews()));
RatingBar rating = (RatingBar)v.findViewById(R.id.ratAvg);
rating.setRating(company.getAvgRating());
break;
}
return v;
}
}
public int getViewTypeCount() {
return 5;
}
public boolean hasStableIds() {
return true;
}
public boolean isEmpty() {
return false;
}
public void registerDataSetObserver(DataSetObserver arg0) {
}
public void unregisterDataSetObserver(DataSetObserver arg0) {
}
public boolean areAllItemsEnabled() {
return false;
}
public boolean isEnabled(int position) {
if (arrayContains(headerPositions, position)) return false;
return true;
}
private Boolean arrayContains(int[] array, int element) {
for (int i=0; i<array.length; i++) {
if (array[i] == element) return true;
}
return false;
}
}
I attached the sources to the Android 2.3.3 library from here. I didn't find out anything else that the stack trace didn't show me. As you all know the ListView
recycles views. There is a collection of some kind that holds these recycled views. When a view dissappears it is added there (I think). This happens at at android.widget.AbsListView$RecycleBin.addScrapView(AbsListView.java:4540)
Somewhere here it gives me an exception that something is out of bounds. I don't have the slightest clue what. But I don't have the patience to debug another 1000 lines of code so I think I will use a ScrollView for now.
It seems it is this line: (AbsListView.class:4540 mScrapViews[viewType].add(scrap);
mScrapViews is of type private ArrayList<View>[] mScrapViews;
. It is an array of lists of type View
. I can try to watch viewType
and scrap
variables but it says errors_during_evaluation
. Probably too dumb to evaluate.
Found the problem. View types had to begin at 0, mine starts at 1.
Android is using view types I provided to access an array by index somewhere. SomeArraySomewhere[myViewType]
The size of this array probably equals the number of view types. So this means I have to start my view types at 0.
Changing this:
public int getViewTypeCount()
{
return 2;
}
to this helped me:
public int getViewTypeCount()
{
return 3;
}
Explanation:
Initially I had 2 view types, but later on in the implementation, I added a third. I started getting that exception, because I forgot to update the method getViewTypeCount() to return 3 instead of just 2.
Hope this helps someone :)
It is possible that position
is:
- a:
null
- b:
higher then the amount of headerResources
?
In this line (getView):
String value = context.getText(headerResources[position]).toString();
First learn to debug: here is a nice video tutorial
The reason, is most likely because the getItemViewType method is returning the wrong values! Each row in listview is a one View. While scrooling getItemViewType reach more than View's count.
What to do? How to avoid issue?
First determine view(row) in your listview that is shown first.While Scrolling use moduler equation
@Override
public int getItemViewType(int position) {
return choseType(position);//function to use modular equation
}
@Override
public int getViewTypeCount() {
return 10;
}
In this example there is ten view(10 row).
private int choseType(int position){
if(position%10==0)
return 0;
else if(position%10==1)
return 1;
else if(position%10==2)
return 2;
else if(position%10==3)
return 3;
else if(position%10==4)
return 4;
else if(position%10==5)
return 5;
else if(position%10==6)
return 6;
else if(position%10==7)
return 7;
else if(position%10==8)
return 8;
else
return 9;
}
Important
Some users mentioned on another question on stackoverflow that method
public int getViewTypeCount() and public int getItemViewType(int position) fix like Tooglebutton automaticly enable state check true on scrooling..that is big wrong .If you dont want automatic enbale on scrool just do
toogleButton.setChecked(false);
on getView override method.
精彩评论