I'm new to android programming and doing the first steps with Adapters (for a ListView).
Overriding the Adapter.getView I often see things like this:
public View getView(int position, View convertView, ViewGroup parent) {
View itemView = null;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) parent.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
itemView = inflater.inflate(R.layout.table_row, null);
} else {
itemView = convertView;
}
// play with itemView
return itemView;
}
My question is what speaks against this:
public View getView(int position, View convertView, ViewGroup parent) {
View itemView = super(position, convertView, parent);
// play with itemView
return itemView;
}
This seems to work for me but I'm sure there's a big point I'm missing :D
Thanks for reading and sorry for my bad english ...
You can use
View itemView = super(position, convertView, parent);
if only you are deriving from "ready to use" adapters (not BaseAdapter), like SimpleAdapter, or ArrayAdapter, as they already have their implementation for the getView().
Have a look at them: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.4_r2.1/android/widget/ArrayAdapter.java#361 for the ArrayAdapter, and
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.4_r2.1/android/widget/SimpleAdapter.java#113 for SimpleAdapter.
If you derive from BaseAdapter, you will have to manualy implement the whole method, as you've described in the first example, because it does not have it out of the box: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.4_r2.1/android/widget/BaseAdapter.java#BaseAdapter
The getView(..)-method of the Adapter can be multiple ways. The only question is, which one is the most efficient?
An interesting article to read and make you understand the ListView more detailed: http://lucasr.org/2012/04/05/performance-tips-for-androids-listview/
If you mean that this piece of code:
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) parent.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
itemView = inflater.inflate(R.layout.table_row, null);
} else {
itemView = convertView;
}
seems unnecessary for you: this piece of code allows Android to create a relatively small number of cells (equals to the number of cells that are visible on your screen +-), and then "recycle" these cells - use them over and over again while the user scrolls the list, instead of creating a cell for each object in your array.
This will help you with:
Saving memory - because you don't create view for each element in your array
Saving CPU usage - creating a view object out of xml file ("inflating") is relatively expensive task and doing so for each item in your array might choke your UI thread
Related
I have learned earlier about great approach to increase performance - Holder Pattern. This is good idea to speed up UI and animation.
It is clearly why and how to use it.
I have used it a lot , but now I am little bit confused about this.
When getView method is called it has three arguments one is converView. As I undertand it is previously inflated view of list item, so the are some questions about this.
If it is previously inflated view, why not just to use it, return it from method, of course check to null before.
How does this implemented,listview class has private array or another data structure that holds all inflated views ?
Why this feature is not implemented in adapters ?
Thanks in advance.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder =(ViewHolder) convertView.getTag();
}
If you would just use the convertView, you would need to get hold of your views with findViewById(). This is exactly what the ViewHolder pattern is trying to avoid. findViewById() is a surprisingly expensive method and can slow down your app, especially if you constantly call it when scrolling through lists.
Listviews reuse the layouts of the child items, to avoid having to inflate the same views over and over again.
Most adapters were already available to developers before people came up with the ViewHolder pattern. The latest new list view, RecyclerView, has an adapter that enforces the use of the ViewHolder pattern.
You can't call ViewHolder holder =(ViewHolder) convertView.getTag(); at the first line of getView. Because convertView could be null. Try again like this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHoldler holder = null;
if (convertView == null) {
convertView = LayoutInflater.from(ctx).inflate(
R.layout.frag_home_gridview_item, null, false);
holder = new ViewHoldler();
holder.iv = (ImageView) convertView
.findViewById(R.id.gridview_item_label);
holder.tv = (TextView) convertView
.findViewById(R.id.gridview_item_name);
convertView.setTag(holder);
} else {
holder = (ViewHoldler) convertView.getTag();
}
holder.tv.setText(getItem(position));
holder.iv.setImageResource(this.ids[position]);
return convertView;
}
private class ViewHoldler {
ImageView iv;
TextView tv;
}
Because you most likely are not using same views. Say you have a row which has a TextView in it. The convertview is one of the recycled views and it's textview may be displaying different information that it should be.
It does keep as many views as there are visible, once you scroll down the top view get's recycled and you come back to answer 1.
I don't understand, your code is from the BaseAdapter class.
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 :)
I defined a Adapter which extends BaseAdapter when I use ListView to display something.I overrided View getView (int position, View convertView, ViewGroup parent) method to reuse View component, in the method, I also wrote if(convertView == null) {System.out.println("test");} block. There are 50 rows data in ListView and the screen only can display about 20 rows data. when I ran the application, LogCat printed less than 50 rows of "test" though I slided the screen to make sure all data are loaded.But why ? I think it should print 50 rows data. Here is the key code:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
convertView = inflater.inflate(resource, null);
System.out.println(++count + "convertView == null:" + convertView);
}
}
someone help me please, I am a newbie.... thanks
Android does not inflate a View for every item in your adapter. It reuses inflated views previously used for other items.
The pattern for binding views in a adapter is something like this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if(convertView == null) {
view = inflater.inflate(resource, null);
} else {
view = convertView;
}
// bind data to view here
return view;
}
In fact you normally would use a ViewHolder class. But first fix your basic adapter before reading about that.
Because convertView will be null only if there isn't a previously returned view that can be recycled (i.e. it's no longer on screen).
The views you return from an adapter's getView() can be recycled in later calls to getView(). The framework passes such recyclable views in via the convertView arg.
So your convertView == null branch only gets run enough times to fill the listview screen once and after that, when scrolling, these old views get recycled.
You missing return view.
ListView recycles view. How ListView's recycling mechanism works
Use a ViewHolder pattern
http://developer.android.com/training/improving-layouts/smooth-scrolling.html
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null) {
convertView = inflater.inflate(resource, null);
holder = new ViewHolder();
holder.tv = (TextView)convertView.findViewById(R.id.textView1);
//initialize views
convertView.setTag(holder);
} else {
holder =(ViewHolder) convertView.getTag();
}
// update your view here
holder.tv.setText("hi");
return convertView;
}
static class ViewHolder {
// YourViews Declaration
TextView tv; // an example
}
I have a question that after searching SO and Google I can't find an answer to. When working with an adapter in Android its good practice to reuse the list item view by using the convertView parameter in the getView() method. My question is this, if I make a change to convertView, will that change persist to future calls to getView()?
For example:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
ViewHolder holder = new ViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.list_item, null);
convertView.setTag(holder);
// if i call this method here, will all future views passed into convertView
// also have this set??
// From what I know about Java and objects I would guess yes
// but I'm not 100% sure how Android processes the convertView behind the scenes
((ViewGroup) convertView).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
}
ViewHolder holder = (ViewHolder) convertView.getTag();
// currently setDescendantFocusability is called here,
// I want to move it to where it above to help improve performance
return convertView;
}
I think so.
The converted views you operate will be reused in future.
When you get a converted view passed by getView() method, the specific converted view might have been used before, so make sure to update all the attributes which might be dirty for current converted view.
Should be kind of below code:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if(view == null){
view = createSpecificView();
}
updateSpecificView(view);//update all attributes here.
return view;
}
Hopefully can help you.
The ONLY reason you need to return the convertView at the end of getView() is for the situation where it was null and/or you create a new instance. When an object is passed as a parameter, you can modify the underlying object's state but cannot change or create a new object. You can't change which object convertView points to.
So, you also return the convertView at the end of the method. This covers the situation where you create a new object.
I have a ListView, and I have added a header (with getListView().addHeaderView) that simply contains a TextEdit widget.
Then when I tap the TextEdit to start writting, the keyboard appears and it messes up the list!
If I tap everywhere else to hide the keyboard, the list messes up again!
I don't know why is this happening. I thought it was something related with the onConfigurationChanged method, but after implementing it (and adding the corresponding attribute in the manifest file) the problem persists.
How could I fix it? Why is Android messing up my list?
EDIT:
My list uses a custom adapter, this is the getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v != null) {
return v;
}
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.list_row, null);
ListTask list_item = items.get(position);
if (list_item != null) {
TextView item_name = (TextView) v.findViewById(R.id.item_name);
item_name.setText(list_item.getTitle());
}
return v;
}
The problem is not the value of my items, but their order. They are displayed in a different order when the keyboard appears, but the values are correct.
EDIT2:
Ok, I have changed my getView method with rekaszeru's suggestion and now it works as expected. But now I'm facing another problem: what if my items have two textviews?
Let's say the second textview is optional, and "Item 1" and "Item 3" have it, but "Item 2" does not, so it's initialized as a void String (length == 0).
The first time the list is displayed, it shows "Item1" and "Item 3" with their second textview, and "Item 2" without it. That's correct. But when the keyboard appears, the "Item 2" takes the second textview of another item and displays it!
This is the modified code I have right now:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.list_row, null);
}
ListTask list_item = items.get(position);
TextView item_name = (TextView) convertView.findViewById(R.id.item_name);
TextView item_optional_text = (TextView) convertView.findViewById(R.id.item_optional_text);
item_name.setText(list_item.getTitle());
// if the item has defined the optional text, make some room and display it
if (item_optional_text.isNotEmpty()) {
LayoutParams layout_params = (LayoutParams) item_name.getLayoutParams();
layout_params.topMargin = 10;
layout_params.height = -2; // -2: wrap_content
item_name.setLayoutParams(layout_params);
item_optional_text.setText(list_item.getOptionalText());
}
return convertView;
}
The isNotEmpty() does this in the Item class:
public boolean isNotEmpty() {
return this.optional_text.length() > 0;
}
Maybe it's too complex to understand in a written question. If so, I can make a short video showing the problem and my source code. Thanks in advance for your help guys.
Your row recycling is messed up. Android is not changing the order of the items, you are.
Right now, if you are passed a row to recycle, you return it without modification. This is a mistake. You are supposed to modify the contents of the row to reflect the data at the supplied position. The only piece of logic you can skip in this case is inflating a brand-new row.
Here is a free excerpt from one of my books that goes through all of this.
You should override the getView method in your ListAdapter implementation, and make sure that you always assign a new value to the view that you are returning (or at least always update it to contain the proper data).
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
convertView = inflater.inflate(R.layout.list_row, parent, false);
//set the necessary data in your TextViews, Checkboxes, etc...
return convertView;
}
If you don't inflate your item renderer, then you can instantiate it from code, like:
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
convertView = new TextView([...]);
convertView.setText(textBasedOnYourData);
return convertView;
}
Edit
As #CommonsWare noted, attention should be payed to the recycling of your list item renderer. So instead of instantiating it every time, you should check whether it already exists or not, and update the underlying TextView afterwards.
So I'd suggest give a try to this slightly modified getView implementation:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.list_row, null);
}
ListTask list_item = items.get(position);
TextView item_name = (TextView) convertView.findViewById(R.id.item_name);
//the item should never be null, but just in case:
item_name.setText((list_item == null) ? "" : list_item.getTitle());
return convertView;
}