I have a listview that for displaying detail data. I'm storing my data in an ArrayList of Strings. However, some of the fields may not have any data to display, but I need to keep the array length the same to match a static titles array. I can trap the empty data in my getView method in my custom base adaptor here:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.drug_detail_cell, parent, false);
}
// check array bounds
if (position < getCount()) {
// check for null items
String item = items.get(position);
if (item != null) {
// get the text views
TextView title = (TextView) convertView.findViewById(R.id.item_title);
TextView sub = (TextView) convertView.findViewById(R.id.item_subtitle);
title.setText(titles.get(position));
sub.setText(item.toString());
} else {
// delete row
}
}
return convertView;
}
My problem is that while the data does not display, I still have an empty row in my listview. My question is how do I delete that row? Any help would be greatly appreciated. Thanks in advanced.
For removing a row from the CustomListAdapter:
Remove the item from the ArrayAdapter from the specified index, after that call notifyDatasetChanged . It will update your listView.
In CustomAdapterClass:
#Override
public void remove(String object) {
super.remove(object);
// your other code
}
In ListActivity class:
CustomAdapterClass adap = new CustomAdapterClass();
adap.remove("hello world");
adap.notifyDatasetChanged(); // this will update your listView
My code is a bare bone example to depict how to achieve your goal.
I have a tip: in else clause you return a empty view
else{
View v = new View(context);
v.setLayoutParams(new AbsListView.LayoutParams(0, 0));
return v;
}
But if your list have divider, the divider below the empty view will be double.
In a different: I think you should handle all null data before getView call. I mean:
- In getCount(){
loop and create a new map from position and not null data
loop and count all data!=null; return count;
}
use new map in getView function.
Hope this help.
I am building a ListView with sections according to the technique described at http://bartinger.at/listview-with-sectionsseparators/ . But I would like to extend it by reusing convertView for the non-section items. However, I am finding that the convertView variable is null each time getView() method is entered. Could someone explain why this is the case?
ViewHolder holder;
final ListViewItem item = items.get(position);
if (item.isSection()) {
Section section = (Section)item;
convertView = inflater.inflate(R.layout.section, null);
TextView title = (TextView) convertView.findViewById(R.id.section_title);
title.setText(section.title);
} else {
if (convertView == null) {
Log.d("Adapter", "convertView was null");
}
Server server = (Server)item;
convertView = inflater.inflate(R.layout.server_row, null);
holder = new ViewHolder();
holder.serverName = (TextView) convertView.findViewById(R.id.server_name);
holder.serverStatusIcon = (ImageView)convertView.findViewById(R.id.server_status_icon);
convertView.setTag(holder);
holder.serverName.setText(server.name);
}
return convertView;
The list is being created and displayed without errors and contains both sections and non-section items just fine.
Are you implementing correctly
getItemViewType (int position) ?
See from Android's documentation:
Returns
An integer representing the type of View. Two views should share the same type if one can be converted to the other in getView(int, View, ViewGroup). Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.
So maybe the convertView is always null because the adapter doesn't know which items belong together, so it doesn't know which ones pass to be recycled...
Try this:
#Override
public int getItemViewType(int position) {
if (((MyItem)getItem(position)).isHeader()) {
return 1;
} else {
return 0;
}
}
#Override
public int getViewTypeCount() {
return 2;
}
The index which you return in getItemViewType is just an identifier to group headers and not-headers together.
In this case you have to implement a method "isHeader" (or analogous) in your model items.
Thank you to Ixx for jogging my mind: what I hadn't noticed was that my list was so short it never actually filled the screen so no recycling was taking place.
For completeness sake, I will add that if you create multiple view types, getView() does return convertView - even if you do not override getItemViewType() and getViewTypeCount() - according to their default implementation (below). Of course, it is likely not the behaviour you want.
public int getItemViewType(int position) {
return 0;
}
public int getViewTypeCount() {
return 1;
}
I just went through this with a GridView that I created. I had problems when I tried to assign a newly inflated view to convertView. The generic structure I adopted was
public View getView(int position, #Nullable View convertView, ViewGroup parent) {
View newView = null;
TextView someText;
// Test to see if there is already a view, if not create one, else use what is
// already existant in convertView
if (null == convertView) {
// inflate your view type into newView here
newView = myInflater.inflate(R.layout._________);
// Get all of your subviews you wish to edit here from newView
someText = (TextView)newView.findViewById(R.id._______);
}else{
// Get all of your subviews you wish to edit here from convertView
someText = (TextView)convertView.findViewById(R.id._______);
}
// Perform all re-alignments, view layouts etc... here
// Perform all updating of subviews data here
// Return structure
if (null == convertView) {
return newView;
} else {
return convertView;
}
}
Hope this helps!
I have a list view adapter that uses different types of view rows.
Most of the time it works fine. But when I remove an element from the list it crashes. It sends a convertView of the incorrect type to getView
public View getView(int position, View convertView, ViewGroup patent) ...
But getItemViewType is returning the correct type.
public int getItemViewType(int position)
so I see something that looks like this
give me the type for position 1 -> returns correct type (say 1)
give me a view for position 1 with a content view for the wrong type (say type 2.)
Any ideas?
Try overiding getViewTypeCount()
#Override
public int getViewTypeCount() {
return types;
}
Return the number of types of Views that will be created by getView(int, View, ViewGroup). Each type represents a set of views that can be converted in getView(int, View, ViewGroup).
When implemented right, the ListView guarantees that the view provided as the convertView is from the right Type
/**
* #return A view from the ScrapViews collection. These are unordered.
*/
View getScrapView(int position) {
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
} else {
int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
}
return null;
}
You should override getViewTypeCount(), returning the number of view types you have and override getItemViewType(int position) returning the view type in the range from 0 to getViewTypeCount() - 1
That's normal, if you get a View with different type in convertView, you would create a new View, and not reuse convertView.
Probably there are no reusable views with the given type.
Note: This answer is from 2011 and might no longer apply.
I had the same issue and it resulted in crashes or unexpected behavior.
This how I fixed my issue:
getViewTypeCount() should return the number of different type of rows in the view
getItemViewType(...) should return the type of the item at position
getView(...) should set a enum type on view when inflated
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
return mlistItems.get(position).type.ordinal();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ListItem item = mListems.get(position);
if (convertView == null) {
switch (item.type) {
case Header:
converview = // Inflate Header Row
break;
case Content:
converview = // Inflate Content Row
break;
}
}
switch (item.type) {
case Header:
//Set header row content
break;
case Content:
//Set content row content
break;
}
return convertView;
}
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.
I have a ListView which displays news items. They contain an image, a title and some text. The image is loaded in a separate thread (with a queue and all) and when the image is downloaded, I now call notifyDataSetChanged() on the list adapter to update the image. This works, but getView() is getting called too frequently, since notifyDataSetChanged() calls getView() for all visible items. I want to update just the single item in the list. How would I do this?
Problems I have with my current approach are:
Scrolling is slow
I have a fade-in animation on the image which happens every time a single new image in the list is loaded.
I found the answer, thanks to your information Michelle.
You can indeed get the right view using View#getChildAt(int index). The catch is that it starts counting from the first visible item. In fact, you can only get the visible items. You solve this with ListView#getFirstVisiblePosition().
Example:
private void updateView(int index){
View v = yourListView.getChildAt(index -
yourListView.getFirstVisiblePosition());
if(v == null)
return;
TextView someText = (TextView) v.findViewById(R.id.sometextview);
someText.setText("Hi! I updated you manually!");
}
This question has been asked at the Google I/O 2010, you can watch it here:
The world of ListView, time 52:30
Basically what Romain Guy explains is to call getChildAt(int) on the ListView to get the view and (I think) call getFirstVisiblePosition() to find out the correlation between position and index.
Romain also points to the project called Shelves as an example, I think he might mean the method ShelvesActivity.updateBookCovers(), but I can't find the call of getFirstVisiblePosition().
AWESOME UPDATES COMING:
The RecyclerView will fix this in the near future. As pointed out on http://www.grokkingandroid.com/first-glance-androids-recyclerview/, you will be able to call methods to exactly specify the change, such as:
void notifyItemInserted(int position)
void notifyItemRemoved(int position)
void notifyItemChanged(int position)
Also, everyone will want to use the new views based on RecyclerView because they will be rewarded with nicely-looking animations! The future looks awesome! :-)
This is how I did it:
Your items (rows) must have unique ids so you can update them later. Set the tag of every view when the list is getting the view from adapter. (You can also use key tag if the default tag is used somewhere else)
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = super.getView(position, convertView, parent);
view.setTag(getItemId(position));
return view;
}
For the update check every element of list, if a view with given id is there it's visible so we perform the update.
private void update(long id)
{
int c = list.getChildCount();
for (int i = 0; i < c; i++)
{
View view = list.getChildAt(i);
if ((Long)view.getTag() == id)
{
// update view
}
}
}
It's actually easier than other methods and better when you dealing with ids not positions! Also you must call update for items which get visible.
get the model class first as global like this model class object
SampleModel golbalmodel=new SchedulerModel();
and initialise it to global
get the current row of the view by the model by initialising the it to global model
SampleModel data = (SchedulerModel) sampleList.get(position);
golbalmodel=data;
set the changed value to global model object method to be set and add the notifyDataSetChanged its works for me
golbalmodel.setStartandenddate(changedate);
notifyDataSetChanged();
Here is a related question on this with good answers.
The answers are clear and correct, I'll add an idea for CursorAdapter case here.
If youre subclassing CursorAdapter (or ResourceCursorAdapter, or SimpleCursorAdapter), then you get to either implement ViewBinder or override bindView() and newView() methods, these don't receive current list item index in arguments. Therefore, when some data arrives and you want to update relevant visible list items, how do you know their indices?
My workaround was to:
keep a list of all created list item views, add items to this list from newView()
when data arrives, iterate them and see which one needs updating--better than doing notifyDatasetChanged() and refreshing all of them
Due to view recycling the number of view references I'll need to store and iterate will be roughly equal the number of list items visible on screen.
int wantedPosition = 25; // Whatever position you're looking for
int firstPosition = linearLayoutManager.findFirstVisibleItemPosition(); // This is the same as child #0
int wantedChild = wantedPosition - firstPosition;
if (wantedChild < 0 || wantedChild >= linearLayoutManager.getChildCount()) {
Log.w(TAG, "Unable to get view for desired position, because it's not being displayed on screen.");
return;
}
View wantedView = linearLayoutManager.getChildAt(wantedChild);
mlayoutOver =(LinearLayout)wantedView.findViewById(R.id.layout_over);
mlayoutPopup = (LinearLayout)wantedView.findViewById(R.id.layout_popup);
mlayoutOver.setVisibility(View.INVISIBLE);
mlayoutPopup.setVisibility(View.VISIBLE);
For RecycleView please use this code
I used the code that provided Erik, works great, but i have a complex custom adapter for my listview and i was confronted with twice implementation of the code that updates the UI. I've tried to get the new view from my adapters getView method(the arraylist that holds the listview data has allready been updated/changed):
View cell = lvOptim.getChildAt(index - lvOptim.getFirstVisiblePosition());
if(cell!=null){
cell = adapter.getView(index, cell, lvOptim); //public View getView(final int position, View convertView, ViewGroup parent)
cell.startAnimation(animationLeftIn());
}
It's working well, but i dont know if this is a good practice.
So i don't need to implement the code that updates the list item two times.
exactly I used this
private void updateSetTopState(int index) {
View v = listview.getChildAt(index -
listview.getFirstVisiblePosition()+listview.getHeaderViewsCount());
if(v == null)
return;
TextView aa = (TextView) v.findViewById(R.id.aa);
aa.setVisibility(View.VISIBLE);
}
I made up another solution, like RecyclyerView method void notifyItemChanged(int position), create CustomBaseAdapter class just like this:
public abstract class CustomBaseAdapter implements ListAdapter, SpinnerAdapter {
private final CustomDataSetObservable mDataSetObservable = new CustomDataSetObservable();
public boolean hasStableIds() {
return false;
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
public void notifyItemChanged(int position) {
mDataSetObservable.notifyItemChanged(position);
}
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
public boolean areAllItemsEnabled() {
return true;
}
public boolean isEnabled(int position) {
return true;
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent);
}
public int getItemViewType(int position) {
return 0;
}
public int getViewTypeCount() {
return 1;
}
public boolean isEmpty() {
return getCount() == 0;
} {
}
}
Don't forget to create a CustomDataSetObservable class too for mDataSetObservable variable in CustomAdapterClass, like this:
public class CustomDataSetObservable extends Observable<DataSetObserver> {
public void notifyChanged() {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {#link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {#link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
public void notifyInvalidated() {
synchronized (mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onInvalidated();
}
}
}
public void notifyItemChanged(int position) {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {#link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {#link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
mObservers.get(position).onChanged();
}
}
}
on class CustomBaseAdapter there is a method notifyItemChanged(int position), and you can call that method when you want update a row wherever you want (from button click or anywhere you want call that method). And voila!, your single row will update instantly..
My solution:
If it is correct*, update the data and viewable items without re-drawing the whole list. Else notifyDataSetChanged.
Correct - oldData size == new data size, and old data IDs and their order == new data IDs and order
How:
/**
* A View can only be used (visible) once. This class creates a map from int (position) to view, where the mapping
* is one-to-one and on.
*
*/
private static class UniqueValueSparseArray extends SparseArray<View> {
private final HashMap<View,Integer> m_valueToKey = new HashMap<View,Integer>();
#Override
public void put(int key, View value) {
final Integer previousKey = m_valueToKey.put(value,key);
if(null != previousKey) {
remove(previousKey);//re-mapping
}
super.put(key, value);
}
}
#Override
public void setData(final List<? extends DBObject> data) {
// TODO Implement 'smarter' logic, for replacing just part of the data?
if (data == m_data) return;
List<? extends DBObject> oldData = m_data;
m_data = null == data ? Collections.EMPTY_LIST : data;
if (!updateExistingViews(oldData, data)) notifyDataSetChanged();
else if (DEBUG) Log.d(TAG, "Updated without notifyDataSetChanged");
}
/**
* See if we can update the data within existing layout, without re-drawing the list.
* #param oldData
* #param newData
* #return
*/
private boolean updateExistingViews(List<? extends DBObject> oldData, List<? extends DBObject> newData) {
/**
* Iterate over new data, compare to old. If IDs out of sync, stop and return false. Else - update visible
* items.
*/
final int oldDataSize = oldData.size();
if (oldDataSize != newData.size()) return false;
DBObject newObj;
int nVisibleViews = m_visibleViews.size();
if(nVisibleViews == 0) return false;
for (int position = 0; nVisibleViews > 0 && position < oldDataSize; position++) {
newObj = newData.get(position);
if (oldData.get(position).getId() != newObj.getId()) return false;
// iterate over visible objects and see if this ID is there.
final View view = m_visibleViews.get(position);
if (null != view) {
// this position has a visible view, let's update it!
bindView(position, view, false);
nVisibleViews--;
}
}
return true;
}
and of course:
#Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final View result = createViewFromResource(position, convertView, parent);
m_visibleViews.put(position, result);
return result;
}
Ignore the last param to bindView (I use it to determine whether or not I need to recycle bitmaps for ImageDrawable).
As mentioned above, the total number of 'visible' views is roughly the amount that fits on the screen (ignoring orientation changes etc), so no biggie memory-wise.
In addition to this solution (https://stackoverflow.com/a/3727813/5218712) just want to add that it should work only if listView.getChildCount() == yourDataList.size();
There could be additional view inside ListView.
Example of how the child elements are populated: