I have a custom adapter derived from ArrayAdapter that overrides getView() that we'll call CustomObjectAdapter. I manually add Views created by the adapter to a LinearLayout like below:
m_adapter = new CustomObjectAdapter(this.getApplicationContext(),
R.layout.custom_object_layout,
m_customObjectArrayList);
m_linearLayout = (LinearLayout)findViewById(R.id.custom_object_list_linear_layout);
for (int i = 0; i < m_adapter.getCount(); i++) {
// Create the view for the custom object
View view = m_adapter.getView(i, null, null);
m_linearLayout.addView(view);
}
I populate a LinearLayout with Views instead of using a ListView, because I it's contained within a Fragment in which I want the entire area to be scrollable--rather than just the area within the ListView. I found this solution in this StackOverflow question: Using a ListAdapter to fill a LinearLayout inside a ScrollView layout.
In the same class that these Views get populated, the data used in the adapter's getView() method is updated and then I call m_adapter.notifyDataSetChanged(). The getView() function is not getting called subsequently, however. In another area of the code I am updating a adapter that's hooked up to a ListView in the same manner (instead of a LinearLayout), and it is updating appropriately. Does anyone know what I'm doing wrong?
An alternative rather than a solution, but you could use the addHeaderView and addFooterView methods in ListView to make non-list items also be scrollable.
LinearLayouts aren't built to use adapters. The reason getView isn't being called is because there's no view connected to the adapter to tell it that new views need to be generated.
In your code there's no direct connection between the adapter and the view as there is when you call setAdapter on a ListView. As far as the LinearLayout's concerned, the views in it could be coming from anywhere. Similarly, the adapter can't really do anything with the news that its dataset changed because it's not connected to anything that asks it to generate views.
Views are regenerated with the ListView because it registers a DataSetObserver with the adapter and then requests new views from the adapter when notified of changes via that observer.
There's no comparable mechanism for a LinearLayout, so your only real option is to empty the views out of it and put them in again manually like you do initially.
Related
I'm curious if I can create a footer for my custom listview in my arrayAdapter class?
What I'm hoping to get out of this footer are a subtotal, tax, and total textview that will automatically sum and do "math" on my listview's "Order" when new items are applied to it.
Would doing it in the arrayAdapter be the best approach? Or should I do it inside the fragment?
I was trying to do it inside the fragment but was running into difficulty with getting data from the row's of the listview.
Just to clarify; the listview is inside its own fragment, it will be pretty alone aside from the textviews I mentioned above.
If in the fragment, which part of the lifecycle should I put it in? I haven't set up onClickListeners just yet, but I feel putting everything in onCreateView() would be incorrect since I will be adding things dynamically.
Edit: The problem I was having while doing it in my fragment was that the getListviewChildren was returning empty; I'm assuming cause I was using it in my onCreateView?
Instead of implementing it yourself in the adapter you can use ListView's own implementaion for a footer view. just call addFooterView on the list and pass it the view you want as a footer.
ListView listView = .. //create your list, or inflate it from xml...
View footerView = ... //create your view, or inflate it from xml...
listView.addFooterView(footerView);
listView.setAdapter(myAdapter);
2 things you should know though: It is best to call this method before using setAdapter on the list, because on all but the latest version of android calling it after will not have any affect. Another thing is that doing so is implemented by wrapping your adapter inside another adapter, so after using this you cannot use getAdapter() to get your original adapter.
For more info go to: http://developer.android.com/reference/android/widget/ListView.html#addFooterView%28android.view.View%29
I have been using using listview since long but suddenly I experienced some of its random behaviour.
When I call invalidateViews() on listviews, in some cases it re-inflates the views for each row and in some cases it doesn't. This is completely random. Can you tell me exactly how does it works?
Ideally it should just refresh the data inside views(rows) and not inflate them again.
Thanks
Calling invalidateViews() on a ListView should not re-inflates the ListView item views. The only way to force the ListView to re-inflates its item views, meaning that the recycled views are cleared, is by resetting the ListView adapter ( I had to dig in the source code to discover that ).
If you take a look at the invalidateViews() method implementation (source code in AbsListView.java), you will see this (Android API 16) :
/**
* Causes all the views to be rebuilt and redrawn.
*/
public void invalidateViews() {
mDataChanged = true;
rememberSyncState();
requestLayout();
invalidate();
}
The method rememberSyncState() implemented in AdapterView.java stores the ListView's position of the selected item (if any), you can check it out.
The requestLayout() method takes care of the measuring, laying out, and drawing (as far as I know) so nothing here too.
And finally invalidate() is used to force the list view to draw (with the new measurements, ...)
So calling invalidateViews() should not force the list view to re-inflate its views.
Could you be re-setting the adapter somewhere so it re-inflates all the views ?
Actually, listview.invalidateViews() causes all the views to be rebuilt and redrawn. You can come to know this when you look on the description gets displayed in eclipse when you try to select invalidateViews().
adapter's getView() method has parameter convertView which is null first time when you populate a listview, later when you scroll/invalidate your listview convertView is not null so you can use it and dont need to inflate a new row
I have a listview with 4 identical rows. Inside those rows, I have a RelativeLayout which contains a TextView (id : R.id.notif). In my Activity, I use my own ArrayAdapter.
I would like to be able to modify the text of the third row. I tried this but it isn't working.
((TextView)listview.getAdapter().getView(2, null, listview).findViewById(R.id.notif)).setText("50");
Thank you.
Do not use adapter.getView() for that! This method is used internally for the adapter to create the view that gets displayed in the list! The correct way to do this is to modify the underlying data and to refresh the list with adapter.notifyDataSetChanged(). Do not try to access views in the list directly, you don't know if they are visible at the moment or scrolled outside the view.
In Android's ListView widget, the ListView will hold the views which is got from getView method of an Adapter in a inner class RecycleBin.
How can I clear the views in the RecycleBin and force the ListView to recreate all the child views.
Calling invalidate() or invalidateViews() did not do the trick for me (as mentioned in the correct answer). The recycled views were still stored in the ListView. I had to dig in Android source code to find a solution. I checked many methods, including the setAdapter() method of the ListView class (Android API 15) :
#Override
public void setAdapter(ListAdapter adapter) {
// ...
mRecycler.clear();
// ...
}
As you noticed, setting an adapter clears the recycler, which holds all the recycled views in a list view. You do not have to create a new adapter, setting the same adapter is enough to clear the recycled views list in the list view :
Adapter adapter = listview.getAdapter ();
// ... Modify adapter ... do anything else you need to do
// To clear the recycled views list :
listview.setAdapter ( adapter );
If I remember correctly a call to invalidate on a ListView widget will empty the cache of Views which are currently stored. I would advise against emptying the cache of views of a ListView because of the potential performance issues.
If you're not going to use the convertView item then you'll end up with having to build the row view each time(resulting in a lot of objects constructed each time the user scrolls) + the extra memory occupied by the recycled views from the RecycleBin which will never be used anyway.
There are a special method in ListView - reclaimViews(List<View>). It moves all items that are currently in use and in recycle bin to specified list. Widget will request new views for items from Adapter before rendering next time.
You can use reclaimed views, if there are not many changes to item structure, or scrap them completely. For example, i'm using this method to dynamically update background drawable for items when selection color was changed by user.
In my opinion #nekavally offers the best solution. I have a ListView and a SimpleCursorAdapter. Sometimes I need to change the size of the list items. But since the adapter recycles the views, they may appear in the wrong way. So I'm just calling mListView.invalidateViews() after reclaiming the recycled views, and it works just fine.
startAnimation(initialHeight, finalHeight, duration, new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
childView.getLayoutParams().height =
(Integer) animation.getAnimatedValue();
childView.requestLayout();
if (animation.getAnimatedFraction() == 1f) {
mListView.reclaimViews(new ArrayList<View>());
mListView.invalidateViews();
}
}
});
Remove object of recycle bin from ArrayList or the data structure you have used in your custom adapter and also call notifyDataSetChanged method of your adapter.
I have a ListView with custom items - 3 ImageViews and a TextView. I have a call to setItemsCanFocus(true), so I can make the ImageViews clickable. I'm currently using SimpleAdapter to populate the View.
I'd like to trigger the AdapterView's onItemClick event when one of those subviews is clicked. The onItemClickListener receives a view as the second argument and that can be used to identify which subview was clicked. Frankly, I was expecting this to be the default behaviour but it isn't, unfortunately.
Is there any way to implement this behaviour without bluntly breaking encapsulation (i.e. creating an Adapter that holds a reference to its View)?
What is the accepted way of dealing with events from views in list items? How do you keep the Adapter from knowing too much about the ListView?
Unfortunately you have to choose between using onItemClick() or onClick() on individual children. One way to do it however is to make the top-level view of each item clickable.
Setting android:addStatesFromChildren="true" on the listview in your xml will send clicks on the child elements to the onItemClick method in the onItemClickListener connected to your listview.