I'm using addFooterView to add a footer view to a ListView, which is
populated by a CursorAdapter controlled by Loader. Sometimes the
ListView tries to recycle the footer view however (through
CursorAdapter.bindView). This causes either ClassCastException (if
I allow the recycle) or some item views shown as footer view (if I do
not allow the recycle).
If I understand it right, the footer views added by addFooterView are
not supposed to be recycled ("Footer views are special views at the
bottom of the list that should not be recycled during a layout").
So this is probably a bug in Android APIs.
Is there any way to work around this problem? What is the correct way
to add footer views to a ListView populated by CursorAdapter?
Some relevant code:
In the activity:
paletteView = (ListView)findViewById(R.id.palette);
paletteView.addFooterView(new PaletteAdapter.NewSlot(this));
paletteAdapter = new PaletteAdapter(this, null);
paletteView.setAdapter(paletteAdapter);
getLoaderManager().initLoader(0, null, this);
In the adapter (PaletteAdapter):
#Override public void
bindView(View view, Context context, Cursor cursor)
{
if (view instanceof NewSlot)
{
Log.wtf(TAG,
("Recycle NewSlot to ID "
+ cursor.getLong(cursor.getColumnIndex
(DataProvider.Palettes._ID))));
return;
}
final Slot slot = (Slot)view;
// Blah blah...
}
Worked it out by myself.
Thanks to this answer, I overrode the getView function as
following and everything works well. Thanks again to Abhinav. I
should look more into the source code when having problems.
#Override public View
getView(int position, View convertView, ViewGroup parent)
{
if (convertView instanceof NewSlot)
return super.getView(position, null, parent);
else return super.getView(position, convertView, parent);
}
Just in case it helps anyone else, I had the same symptom (where the listview was trying to recycle the footer view), and the reason for this is that I was updating the height of the footer view with this code:
private void setFooterViewHeight(int height) {
LayoutParams layoutParams = new ListView.LayoutParams(LayoutParams.MATCH_PARENT, height);
mFooterView.setLayoutParams(layoutParams);
}
What I didn't realise is that the ListView.LayoutParams is where the viewType is cached, and when I create a new LayoutParams, it's being reset to 0, which means that it's eligible for the view recycle process
What I have now instead is:
private void setFooterViewHeight(int height) {
LayoutParams layoutParams = mFooterView.getLayoutParams();
if(layoutParams == null) {
layoutParams = new ListView.LayoutParams(LayoutParams.MATCH_PARENT, height, ListView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER);
mFooterView.setLayoutParams(layoutParams);
} else {
layoutParams.height = height;
mFooterView.requestLayout();
}
}
Related
As you should know, ListView recycles the view. But i want to work with elements that can be clicked and expanded. Like i already did:
But it was completely messed up, even using:
View checklayout = convertView;
if(checklayout == null){
checklayout = inflater.inflate(R.layout.home_cell, null);
}
When some opened expandable views goes out of the screen, the recycled one, which shouldn't be expandable, receives the vanished's layout. Only view that has "1 AVALIAÇÃO LANÇADA" should open, and show it's content. I add this content by using if(qtdAvaliacoes > 0) that is a property of my Object that comes from ArrayList<>.
I "solved" this disabling the recycler, with:
#Override
public int getViewTypeCount() {
return getCount();
}
#Override
public int getItemViewType(int position) {
return position;
}
Once my listView will only receives 5~10 rows. But i know that isn't a good practice. While i'm writting this question, i found a solution, calling my object before inflate any view, then checking the property:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View checklayout = convertView;
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final Disciplina disciplina = lista.get(position);
if(checklayout == null || disciplina.getQtdAvaliacoes() == 0){
checklayout = inflater.inflate(R.layout.home_cell, null);
}
final View layout = checklayout;
But I don't think this is the best way to do this. I read something about Tags, but was little confused. I think if i could bind these onClick methods to the row position it would be better.
Any ideas ? Or is my solution good at you, developer's, point of view.
Thanks.
The easiest way is to not do subinflates within a list item. Do it via view visibilities instead, making the inflated part GONE if you don't want it to display yet. You'll just have to explicitly set the visibility of that view in every call to getView
I'm facing some trouble with the navigation drawer adapter.
It's supposed to display items as follows: Favorito, Categorias, and small sub categories underneath categorias.
I programmed the navigation drawer adapter to use a big_layout.xml file by default, but if its position is greater than a certain value, then it uses a small_layout.xml file.
It works fine for the first few items, but the problem is when I scroll down to see the rest of the items, they use the big_layout.xml, and then when I scroll back up, the original big items change their view and use the small layout!
below is the code, and this is a screen shot of the bad results: http://i.stack.imgur.com/QWwts.jpg
#Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
LayoutInflater laoutInflater = (LayoutInflater)
context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if (getItemId(position)>3)
view = laoutInflater.inflate(R.layout.drawer_list_item_small, null);
if (getItemId(position)<=3)
view = laoutInflater.inflate(R.layout.drawer_list_item, null);
}
ImageView icon = (ImageView) view.findViewById(R.id.icon);
icon.setImageResource(drawerItems.get(position).getIcon());
TextView title = (TextView) view.findViewById(R.id.title);
title.setText(drawerItems.get(position).getTitle());
return view;}
Is there anything wrong I'm doing ? , Is there something missing that might be responsible of making the view stable?
How can i fix this ?
Your issue is with recycling. When you scroll down and back up, the views using the small layout are no longer needed, and so are eligible for recycling - now, the view is not null, so the layout will not be reinitialised based on its position, but merely updated with the new content.
You can fix this by using ViewTypes in your list adapter class, overriding the following methods.
#Override
public int getItemViewType(int position) {
return (position > 3) ? 0 : 1;
}
#Override
public int getViewTypeCount() {
return 2;
}
Then, in your getView() you will not be given a view (for recycling) if it is of the wrong view type.
#Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
int layout = getLayoutForViewType(position);
view = LayoutInflater.from(parent.getContext()).inflate(layout, null);
}
...
return view;
}
private int getLayoutForViewType(int position) {
if (getItemViewType(position) == 0) {
return R.layout.blahblahblah;
}
return R.layout.bloobloobloo;
}
I have listview with hundred of items. Every item had a couple of LinearLayouts but ONE of them is Visibility.GONE! Every item has textviews and an image. On Image Click i want to set the LinearLayout with visibility.Gone to View.VISIBLE. It works fine until you scroll down the listview, then every 4th item has the same layout set to VISIBLE but i only need the Clicked one! Here is the getView method:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ImageView imgForClick;
View vi = convertView;
if (convertView == null)
vi = inflater.inflate(R.layout.custom_row, null);
final LinearLayout hiddenLayout = (LinearLayout)vi.findViewById(R.id.hiddenLayout);
imgForClick = (ImageView)vi.findViewById(R.id.imageView3);
imgForClick.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
hiddenLayout.setVisibility(View.VISIBLE);
}
});
return vi;
}
That's because you are recycling the views, so the layout gets changed on a click and then that layout is used in your other rows to save memory.
You need to remember the state of each of the rows to know whether or not the layout should be visible or not
Have something like this:
public View getView(final int position, View convertView, ViewGroup parent) {
...
if (shouldBeVisible.get(position)) {
hiddenLayout.setVisibility(View.VISIBLE);
} else {
hiddenLayout.setVisibility(View.GONE);
}
That way the layout will always be set one way or another.
shouldBeVisible is a List of something that lets you know which rows should have that layout visible or not.
EDIT--
An alternative is to remove view recycling, however this will dramatically hurt performance and should NOT be done, but I'm just explaining to list all your options.
You would remove the line
if (convertView == null)
Making Android always inflate a new view, instead of using the recycled one when possible.
I'm trying to give the users of my app the option to change how they want their results displayed.
I've created a different layout item for each view and extended from BaseAdapter like so:
public View getView(int index, View recycledCompatibleView, ViewGroup parent)
{
// Just in case the view can be reused
View toReturn = recycledCompatibleView;
if(toReturn == null)
{
LayoutInflater inflator = LayoutInflater.from(parent.getContext());
toReturn = inflator.inflate(layoutId, null);
}
...
}
public void setResultsListStyle(int layoutId)
{
this.layoutId = layoutId;
}
Calling notifyDataSetChanged() is (observable through debug) refreshing the view because the new view is being inflated and returned from getView() method.
However the view on screen is not changing...
Is there something I'm missing ?
The problem here is that ListView may cache already inflated Views that are of old "format". You need to somehow reset this View "cache". For this you can use next trick:
mListView.setAdapter(mListView.getAdapter());
Instead of calling notifyDataSetChanged().
I am wondering how to manage the views inside a ListView.
I have a custom Adapter that is set on the ListView, this Adapter overrides the getView method
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
v = mInflater_.inflate(R.layout.news_newsentry, null);
}
final NewsItem newsItem = getItem(position);
if (newsItem != null) {
// Do stuff
}
return v;
}
But the thing is that when the user clicks on an item, I slightly change the view to make it bigger. It works well, but when the item view is recycled, it keeps the "big" height to display another item.
To prevent that, I changed the code to create a new View each time
Change:
View v = convertView;
if (v == null) {
v = mInflater_.inflate(R.layout.news_newsentry, null);
}
By
View v = mInflater_.inflate(R.layout.news_newsentry, null);
The problem now is that when the item disappears from the list and reappears (the list is scrolled), the view is completely new and the height is set to "small".
My question then: how to manage the items views to keeps their properties, without messing with the other views and the view recycling?
I think you can get the result you want by using the ListView built in support for more than one view type in a list.
In your adapter you would implement additional methods similar to
#Override
public int getItemViewType(int position) {
int type = 0;
if (position == mySelectedPosition) {
type = 1;
}
return type;
}
#Override
public int getViewTypeCount() {
return 2;
}
Then your getView method will be handed a view of the correct type for the position of the item. Ie, the selected item will always be given a "big" view to re-use.
Creating a new View every time is not recommended for performance and memory reasons.