I'm implementing custom adapter which handles multiple type of lines in a listview based on this (very useful) tutorial: http://logc.at/2011/10/10/handling-listviews-with-multiple-row-types/
Now, I thought I understood everything but one thing puzzles me.
In the getView method we receive the convertView which suppose to be the view (group) with specific layout to display in the specific line in the listview.
public View getView(int position, View convertView, ViewGroup parent) {
//first get the animal from our data model
Animal animal = animals.get(position);
//if we have an image so we setup an the view for an image row
if (animal.getImageId() != null) {
ImageRowViewHolder holder;
View view;
//don't have a convert view so we're going to have to create a new one
if (convertView == null) {
ViewGroup viewGroup = (ViewGroup)LayoutInflater.from(AnimalHome.this)
.inflate(R.layout.image_row, null);
//using the ViewHolder pattern to reduce lookups
holder = new ImageRowViewHolder((ImageView)viewGroup.findViewById(R.id.image),
(TextView)viewGroup.findViewById(R.id.title));
viewGroup.setTag(holder);
view = viewGroup;
}
//we have a convertView so we're just going to use it's content
else {
//get the holder so we can set the image
holder = (ImageRowViewHolder)convertView.getTag();
view = convertView;
}
//actually set the contents based on our animal
holder.imageView.setImageResource(animal.getImageId());
holder.titleView.setText(animal.getName());
return view;
}
//basically the same as above but for a layout with title and description
else {
DescriptionRowViewHolder holder;
View view;
if (convertView == null) {
ViewGroup viewGroup = (ViewGroup)LayoutInflater.from(AnimalHome.this)
.inflate(R.layout.text_row, null);
holder = new DescriptionRowViewHolder((TextView)viewGroup.findViewById(R.id.title),
(TextView)viewGroup.findViewById(R.id.description));
viewGroup.setTag(holder);
view = viewGroup;
} else {
view = convertView;
holder = (DescriptionRowViewHolder)convertView.getTag();
}
holder.descriptionView.setText(animal.getDescription());
holder.titleView.setText(animal.getName());
return view;
}
}
However, in the case of multiple types of lines in the listview (for example, list of animals with separators lines with titles like 'mamals','fish','birds') how does the listview know what convertView to send? it can be one of two completely different types. something is very unclear to me. can someone explain please?
From the tutorial you provided :)
The two additional methods android Adapters provide for managing different row types are:
getItemViewType(int position) and getViewTypeCount().
The list view uses these methods create different pools of views to reuse for different types of rows.
Good Luck :)
Related
I try to add dynamic number of cell inside listview using custom cell layout and ArrayAdapter and it done well. But i face a problem when i want each cell may different follow type of data.
Example i have 3 categories inside a listview :
Video (video_custom_cell.xml)
Photo (photo_custom_cell.xml)
Audio (audio_custom_cell.xml)
if data is video then i use cell number one, else if data is photo i use photo_custom_cell and else i use audio_custom_cell.
What i have tried only can reuse a custom cell for dynamic number of row, but i still not find how to use custom cell follow by type of data inside cell.
Can anyone help me to explain an example for my problem?
Thank you.
You can use a custom adapter for this and inside the getView function you do for example:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView;
if (convertView == null) {
if(items.get(position).type.equals("video")) {
rowView = getActivity().getLayoutInflater().inflate(
R.layout.video_custom_cell, null);
}else if(items.get(position).type.equals("photo")) {
rowView = getActivity().getLayoutInflater().inflate(
R.layout.photo_custom_cell, null);
}
} else {
rowView = convertView;
}
}
I'm adding view holder to the row of ListView to store widgets. Here is how I do it:
public View getView(int position, View row, ViewGroup parent) {
// Check if an existing view is being reused, otherwise inflate the view
if (row == null) {
row = LayoutInflater.from(getContext()).inflate(R.layout.articles_list_row, parent, false);
row.setTag(R.id.articles_list_row_widgets_holder, new TextListRowHolder(row));
}
I'm also reading a book where an author initializes it like this:
ViewHolder holder=(ViewHolder)row.getTag();
if (holder==null) {
holder=new ViewHolder(row);
row.setTag(holder);
}
Here is the quote from the book:
If the call to getTag() on the row returns null, we know we need to
create a new ViewHolder, which we then attach to the row via setTag()
for later reuse.
Is my approach wrong? I assume that if row is null then there is no holder attached, and if the row is already created than the holder is already attached.
no_coding_knowledge is right, you need the key to get the holder back from the tag, so row.getTag(R.id.articles_list_row_widgets_holder); should be the solution to your answer.
Furthermore i would recommend to get rid of the key at all, because i'm pretty sure you won't need it.
So just use row.setTag(holder) and holder = row.getTag() to set/get the holder.
In case you want to stay with your key R.id.articles_list_row_widgets_holder, here is some sample code:
#Override
public View getView(int position, View row, ViewGroup parent) {
TextListRowHolder holder = null;
// Check if an existing view is being reused, otherwise inflate the view
if (row == null) {
row = LayoutInflater.from(getContext()).inflate(R.layout.articles_list_row, parent, false);
holder = new TextListRowHolder(row);
row.setTag(R.id.articles_list_row_widgets_holder, holder);
} else
holder = (TextListRowHolder)row.getTag(R.id.articles_list_row_widgets_holder);
// do something with the holder, like holder.bind(getItem(position))...
}
You are using setTag using a specific key. So if you want to get that you must use the getTag(int key) method to get it.
Usually an Adapter will have this to optimize the performance of the listview:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
System.out.println("getView " + position + " " + convertView);
ViewHolder holder = null;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item1, null);
holder = new ViewHolder();
holder.textView = (TextView)convertView.findViewById(R.id.text);
convertView.setTag(holder);
} else {
holder = (ViewHolder)convertView.getTag();
}
holder.textView.setText(mData.get(position));
return convertView;
}
and view holder is:
public static class ViewHolder {
public TextView textView;
}
But what if i have different type of rows, like 1 with an ImabeView, 1 with a CheckBox, 1 with EditText
1st thing will be:
#Override
public int getViewTypeCount() {
return 3;
}
#Override
public int getItemViewType(int position) {
//if something
return 0
//if something else
return 1
//if something different
return 2
}
and in getView();
getView(int position, View convertView, ViewGroup parent){
//if convetView == null, getItemViewType(position) and depending on the type inflate respective layout
convertView.setTag(holder);
//else
holder = (ViewHolder)convertView.getTag();
}
But what about the ViewHolders, should i have 3 Different ViewHolders and depending on the type........setTag for respective Holder?
I could find any example for something like this. Actually i haven't seen anu ListView using more than 1 ViewHolder.
Am i doing it the right way??
Thank You
There is nothing stopping you from declaring all possible views from all of ListView's layouts in a single ViewHolder class(so the ViewHolder will hold a reference to an ImageView, CheckBox and EditText from your example).
In the getView method when the convertView is null you will set the views in the ViewHolder only for that type of row, all other view references in the ViewHolder will be null. When it's time to use the views from the ViewHolder just see with which type of row your working and only get the views from the ViewHolder that belong to that row.
You could also use three ViewHolder classes for each type of row(and set them for each particular row when you inflate it), but I think the first versions is nicer. In the end you could go either way as long as you properly implement the multiple row types mechanism.
- I believe its not 3 rows, but a Single row with 3 different components.
- Single holder class with 3 different components is what you need.
See this example in the link below:
http://www.ezzylearning.com/tutorial.aspx?tid=1763429
I second Kumar Vivek Mitra.
You need to have 1 row containing all the 3 views on below the other.
If however you want different rows then try this:
- create a custom row and put all the 3 views in it.
- set the visibility of all these views to GONE
- get the reference of all three views in ViewHolder.
- when setting the list, change the visibility of those views which you want to show to VISIBLE.
We are working with list views in college at the moment. My lecturer gave us a simple application that displays mail messages in a list and when the user selects one it displays the content of the message in a new activity. I understand pretty much all of what is going on but there are a few grey areas I want to clear up!
Basically I am wondering what this section of code does?
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.inbox_row, null);
}
This method is located within a class that extends ArrayAdapter. Am I right in thinking that it is some form of recycling? for when views go on and off the screen?....
Any help is much appreciated. thanks.
it's exactly what you said, a form of recycling.
Inflating a layout takes a lot of memory and a lot of time, so for the efficiency sake, the system passes to you that just went off the screen and you can simply update its text and images and give them back to the UI.
So for example, if your list view is showing 6 items on its list (due to the height of it), it will only inflate 6 items and during scroll it just keeps recycling them.
there's some extra optimisations tricks that you should use and I'm sure that the video link that the commenter posted will explain them.
edit
that example is an ArrayAdapter of Store items, but you can make it to whatever you need.
the adapter does the match and separation layer between UI and data.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null)
convertView = newView();
// Store is the type of this ArrayAdapter
Store store = getItem(position);
Holder h = (Holder) convertView.getTag();
// And here I get the data and address them to the UI
// as you can see, if the convertView is not null,
// I'm not creating a new one, I'm just changing text, images, etc
h.storeName.setText(store.getStoreName());
h.address.setText(store.getAddressLine1());
h.postcode.setText(store.getPostCode());
h.distance.setText(store.getDistance());
return convertView;
}
// I like to separate in a different method when the convertView is null
// but that's me being organisation obsessive
// but it also makes easy to see which methods are only being called the 1st time
private View newView() {
LayoutInflater inf = LayoutInflater.from(getContext());
View v = inf.inflate(R.layout.map_result_list, null);
Holder h = new Holder();
// here we store that holder inside the view itself
v.setTag(h);
// and only call those findById on this first start
h.storeName = (TextView) v.findViewById(R.id.txtLine1);
h.address = (TextView) v.findViewById(R.id.txtLine2);
h.postcode = (TextView) v.findViewById(R.id.txtLine3);
h.distance = (TextView) v.findViewById(R.id.txtDistance);
return v;
}
// this class is here just to hold reference to the UI elements
// findViewById is a lengthy operation so this is one of the optimisations
private class Holder {
TextView storeName;
TextView address;
TextView postcode;
TextView distance;
}
i have a ListView with a onClicklListener.
The ListView has a row Layout of say /res/listitem_a
now after an onClickevent of the any listitem , i want to change the layout of
only that listitem to say /res/listitem_b..
any help on how shall i proceed.
Use BaseAdapter and modify in getView call.
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.custom_layout, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// Change text size
holder.text.setTextAppearance(context,R.style.customStyle);
return convertView;
}
static class ViewHolder {
TextView text;
}
And you can use position variable in getView call to change specific row. Hope this help!!!
You can use ViewFlipper as layout of the rows. With ViewFlipper you can specify as many layouts as you want and flip among them when something happen (like a click event). Here is a good tutorial about ViewFlipper.
Moreover, you should implement a custom adapter, extending BaseAdapter, and overriding the getView method.
#Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.your_row_layout, null); //this will inflate the layout into each row
}
//from here on, assign the information to display to the layout widgets
Hope I've helped you.