Unfortunately I really think I have to explain my situation a little bit extensively.
I am writing an android app regarding the ancient Latin language: my goals are showing to users the entire conjugation of latin verbs, and giving them the right verbal analysis when they search for a specific inflected form. This is my manifest.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android_application.app_name"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ConjActivity"
android:label="@string/app_name"
android:parentActivityName=".MainActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.android_application.app_name.MainActivity"/>
</activity>
</application>
</manifest>
Just know that in order to achieve the first goal I automatically create ALL the forms of every verb as strings, and every conjugation is showed as a simple ListView with only one TextView string as item: one item = one inflected verbal form.
Now, I need to customize my items, modifying some times their textStyle, some times their alignment, etc. In order to do so, I created my custom ListAdapter this way:
private static class ConjAdapter extends ArrayAdapter<String> {
private ArrayList<Long> ids;
private HashMap<String, Long> mIdMap;
public ConjAdapter(Context context, int textViewResourceId, List<String> objects) {
super(context, textViewResourceId, objects);
ids = new ArrayList<Long>();
mIdMap = new HashMap<String, Long>();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = super.getView(position, convertView, parent);
ViewHolder holder;
if(convertView==null){
convertView = LayoutInflater.from(context).inflate(R.layout.simple_list_item_1, parent, false);
holder = new ViewHolder();
holder.txt = (TextView) convertView.findViewById(R.id.text1);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
long id = getItemId(position);
if(sel_vb!=null){
if(ids.contains(id)){ v.setBackgroundColor(context.getResources().getColor(R.color.row_bckgr_RED));
} else {
v.setBackgroundColor(Color.TRANSPARENT);
}
}
for(Map.Entry<String, Long> map : mIdMap.entrySet()){
if(id==map.getValue()){
holder.txt.setText(map.getKey());
break;
}
}
String item = (String) holder.txt.getText();
if(item.equals(context.getResources().getString(R.string.ind))||
item.equals(context.getResources().getString(R.string.subj))||
item.equals(context.getResources().getString(R.string.imp))||
item.equals(context.getResources().getString(R.string.inf))||
item.equals(context.getResources().getString(R.string.pt))||
item.equals(context.getResources().getString(R.string.ger))||
item.equals(context.getResources().getString(R.string.gerv))||
item.equals(context.getResources().getString(R.string.sup))){
holder.txt.setGravity(Gravity.CENTER);
holder.txt.setTextSize(20f);
holder.txt.setTypeface(null, Typeface.BOLD);
} else if(item.equals(context.getResources().getString(R.string.pres))||
item.equals(context.getResources().getString(R.string.impf))||
item.equals(context.getResources().getString(R.string.fut))||
item.equals(context.getResources().getString(R.string.pf))||
item.equals(context.getResources().getString(R.string.ppf))||
item.equals(context.getResources().getString(R.string.futant))){
holder.txt.setTypeface(null, Typeface.ITALIC);
holder.txt.setTextSize(19f);
} else {
holder.txt.setPadding(10, 0, 0, 0);
}
return convertView;
}
@Override
public long getItemId(int position) {
String item = getItem(position);
return mIdMap.get(item);
}
@Override
public boolean hasStableIds() {
return true;
}
static class ViewHolder {
TextView txt;
}
}
The problem is that after the 19th item something goes wrong in the sense that I receive no error messages nor the app crashes, but my custom code doesn't work anymore and items, that should have some features, have on the contrary other ones. And this situation worsens when I scroll my list up and down.
After what I read, I really think that this problem regards the recycling goal of the convertview variable called by the getView() of my custom adapter.
And here is my question: why does it happen even with stable ids (that I store in mIdMap along with the associated item strings) and how can I detach my item from the incorrect position variable?
Here is the code with which I populate mIdMap and ids:
HashMap<String, Long> tempMap = conjadapt.mIdMap;
for(int i=0, j=0; i<displ_conj.size(); i++, j++){
tempMap.put(displ_conj.get(i), (long) j);
}
if(sel_vb!=null){
for(Map.Entry<String, Long> map : tempMap.entrySet()){
if(map.getKey().equals(sel_vb))
conjadapt.ids.add(map.getValue());
}
}
where displ_conj is the ArrayList in which I store my data. mIdMap stores a long variables because the getItemId() has to return a long and with that I need to do some stuff somewhere else.
You are right this is a recylcing problem and it has nothing to do with stable Ids. As pointed out by samgak, you should not be invoking the super call:
View v = super.getView(position, convertView, parent);
That will create a whole new View
which you are then modifying the background color. However, since that View
is never returned, it gets tossed to the wind when the method call completes. So basically, your getView
method is doing some extra work that's never used. You should be solely dealing with convertView
. Though, this is not causing the recycling problem you are seeing.
It also looks like you are attempting to show a red background for the sel_vb
. I'm assuming that means selected verb? Know there is a built in mechanism to do this. ListViews
support a method called setItemChecked(position, boolean). Basically you can highlight any position using that method call. Note you'll need to enable a choice mode first. You can change the default blue highlight via styles or by creating your own layout instead of using the Android provided one.
Unfortunately there's not much documentation that really explains how enabling stable Ids affect the ListView
. It's only ever used when enabling a choice mode. It helps ensure the correct item is highlighted/checked during things like screen configurations or mutations to the adapter while something is highlighted/checked. Otherwise, stable ids has nothing to do with View
generation.
Its hard to say exactly what's causing the funky recycling behavior but it's definitely something related to how you populate the text via the Id mapping. Specifically this guy:
for(Map.Entry<String, Long> map : mIdMap.entrySet()){
if(id==map.getValue()){
holder.txt.setText(map.getKey());
break;
}
}
My suggestion would be to completely remove all the Id logic from the getView
method. Instead populate the text by doing:
holder.txt.setText(getItem(position));
One more performance consideration. I suggest looking into implement different view types. For your case, you could have 3 different view types based on this logic:
String item = (String) holder.txt.getText();
//First type
if(item.equals(context.getResources().getString(R.string.ind))||
item.equals(context.getResources().getString(R.string.subj))||
item.equals(context.getResources().getString(R.string.imp))||
item.equals(context.getResources().getString(R.string.inf))||
item.equals(context.getResources().getString(R.string.pt))||
item.equals(context.getResources().getString(R.string.ger))||
item.equals(context.getResources().getString(R.string.gerv))||
item.equals(context.getResources().getString(R.string.sup))){
//Second type
} else if(item.equals(context.getResources().getString(R.string.pres))||
item.equals(context.getResources().getString(R.string.impf))||
item.equals(context.getResources().getString(R.string.fut))||
item.equals(context.getResources().getString(R.string.pf))||
item.equals(context.getResources().getString(R.string.ppf))||
item.equals(context.getResources().getString(R.string.futant))){
} else {
//Third type
}
Then you can create three custom layouts to inflate instead of relying on the built in simple_list_item_1 layout. This improves the View recycling and gets rid of the inflate and later modify logic.