I have created a Custom Adapter for my listview that inflates a custom view, and allows the user to filter the list.
This all works fine, but the problem is the icons I've placed within the listview items are not following the filtered data.
Example
Notice the icons do not change position, but the Names are filtered correctly.
Here is my Custom Adapter that accomplishes the custom listview
I'm using Mono so it's written in C#, but hopefully you java guys will be able to tell what I'm doing here also...
/// <summary>
/// ArrayAdapter to handle displaying student with risk indicator
/// </summary>
private class StudentListAdapter : ArrayAdapter<Student>
{
private IList<Student> items;
private Context outer_context;
public StudentListAdapter(Context context, int resource, int textViewResourceId, IList<Student> items)
: base(context, resource, textViewResourceId, items)
{
this.items = items;
this.outer_context = context;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
LayoutInflater vi = (LayoutInflater)outer_context.GetSystemService(Context.LayoutInflaterService);
View tmpView = vi.Inflate(Resource.Layout.ItemStudent, null);
// call base.GetView that uses Object.ToString() to keep sorting intact
View v = base.GetView(position, tmpView, parent);
Student s = items[position];
if (s != null)
{
// determine correct risk image
ImageView listRisk = v.FindViewById<ImageView>(Resource.Id.listRisk);
if (s.R != null)
{
switch ((int)s.R.Value)
{
case 1:
listRisk.SetImageResource(Resource.Drawable.risk_green);
break;
case 0:
listRisk.SetImageResource(Resource.Drawable.risk_yellow);
break;
case -1:
listRisk.SetImageResource(Resource.Drawable.risk_red);
break;
case -2:
listRisk.SetImageResource(Resource.Drawable.risk_red2);
break;
default:
listRisk.SetImageResource(Resource.Drawable.risk_gray);
break;
}
}
else
{
listRisk.SetImageResource(Resource.Drawable.risk_gray);
}
}
return v;
}
}
UPDATE
After some breakpoints and inspection, I开发者_开发技巧've found that when the list filters it's rebuilding the entire list before filtering
For example when a letter is typed into the filtering field, the first student (s
) that runs through the GetView() process is Heath, Ackerson
which is the first student in the list, even when the filter does not match him.
So, It seems like the filtering is taking place AFTER the GetView's are ran, leaving the images in place... Still not sure how to fix it..
* FULL SOLUTION *
http://jondavidjohn.com/blog/2011/08/android-custom-listview-filtering
I would do the following: Place EditText and ListView in your layout. Use BaseAdapter instead of ArrayAdapter. Actually it's the same, but I don't call base.getView():
private class StudentListAdapter : BaseAdapter
{
private IList<Student> items;
private Context outer_context;
public StudentListAdapter(Context context, IList<Student> items)
{
this.items = items;
this.outer_context = context;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
LayoutInflater vi = (LayoutInflater)outer_context.GetSystemService(Context.LayoutInflaterService);
View v = vi.Inflate(Resource.Layout.ItemStudent, null);
Student s = items[position];
v.FindViewById<TextView>(R.id.text_view_id).setText(s.Name);
// determine correct risk image
ImageView listRisk = v.FindViewById<ImageView>(Resource.Id.listRisk);
if (s.R != null)
{
switch ((int)s.R.Value)
{
case 1:
listRisk.SetImageResource(Resource.Drawable.risk_green);
break;
case 0:
listRisk.SetImageResource(Resource.Drawable.risk_yellow);
break;
case -1:
listRisk.SetImageResource(Resource.Drawable.risk_red);
break;
case -2:
listRisk.SetImageResource(Resource.Drawable.risk_red2);
break;
default:
listRisk.SetImageResource(Resource.Drawable.risk_gray);
break;
}
}
else
{
listRisk.SetImageResource(Resource.Drawable.risk_gray);
}
return v;
}
void displayNewData(IList<Student> new_items)
{
items=new_items;
notifyDatasetChanged();
}
}
So when filter text is changed you just need to put new data to adapter using displayNewData() method:
public class StudentsActivity extends Activity {
public override void onCreate(Bundle savedInstanceState)
{
...
filterEditText.addTextChangedListener(filterTextWatcher);
}
private TextWatcher filterTextWatcher = new TextWatcher()
{
public void afterTextChanged(Editable s) {}
public void beforeTextChanged(CharSequence s, int start, int count,int after) {}
public void onTextChanged(CharSequence filter, int start, int before, int count)
{
IList<Student> items = getStudentsFiltered(filter);
adapter.displayNewData(items);
}
};
//getStudentsFiltered() method may look like that:
getStudentsFiltered(String filter)
{
IList<Student> students_filtered=new List<Students>();
foreach(Student student in students)
if(student.Name.Contains(filter))
students_filtered.Add(student);
return students_filtered;
}
}
I'm pretty sure this approach will work fine. Feel free to ask any questions.
I didn't compile the code so there may be some minor mistakes.
Try instantiating ImageView listRisk = v.FindViewById<ImageView>(Resource.Id.listRisk)
and then setting the resources, in each case.
Check tmpView and v. I don't have the Mono stuff so I can't look at the implementation of ArrayAdapter<>.
I have seen the issue arise because my view ends up getting added to the parent before I do my population stuff. Default implementations of GetView() will add the view to the parent and return the parent, not the newly inflated view. Doing findViewById() on the parent always returned the same ImageView (the first one in the list perhaps) instead of the ImageView in my newly inflated view.
If this is the case don't pass parent to base.GetView(), pass null instead. Add your newly inflated view to the parent at the end of your GetView() implementation.
One thing I've noticed (while coding in Java and not Mono) is that if you try to do something like this:
imageView.setImageResource(R.myResourceID1);
imageView.setImageResource(R.myResourceID2);
The image displayed will always be myResourceID1. To get around this, someone I work with suggested using this code when you want to set an imageView's resource that is already set. So:
imageView.setImageResource(R.myResourceID1);
imageView.setImageResource(0); //supposedly invalidates the image resource
imageView.setImageResource(R.myResourceID2);
Though after reading your update, it seems that if you are filtering after you are setting the imageViews in your getView() method, that this may not be your problem. Thought I would share since setting an already used imageView's resource a 2nd time through getView() sounds like the problem I described above.
精彩评论