So we are supposed to use the recycler view when we want to show a large list of elements.
The benefit of that is that views are re-used so we don't inflate each view in the list and keep them in memory and just keep in memory those that are displayed in the screen recycling the rest.
But what if the recycled view is itself a "mini" list?
I.e. for each item recycled we change its structure by removing all child views and adding new child views?
Is that defeating the whole idea of using a recycled list?
Example in question (itemInRecycler is a vertical LinearLayout that is passed to the recycler view holder):
itemInRecycler.removeAllViews();
for(element: elements) {
CustomView view = inflate();
view.setDisplayData(element);
itemInRecycler.addView(view);
}
Is that defeating the whole idea of using a recycled list?
It depends on how much of the whole itemView the sub-list is. If the sub-list is the only thing you're displaying, then yes, you're defeating a large portion of the performance gains. You're still inflating views every time you bind the ViewHolder, and that's one of the things you try to avoid when using RecyclerView.
It also depends on how large the sub-list is. If it's only three elements at most, then the cost is diminished. If the sublist is hundreds of items, then the cost is large.
One option is to use a sub-RecyclerView instead of a LinearLayout, and to connect each sub-RecyclerView to the same shared RecycledViewPool. This way you get all the benefits of RecyclerView vs a scrollable LinearLayout, but even better because each sub-RecyclerView can take ViewHolders from the others.
If that's a little too heavy-handed for you, you could potentially just optimize the code you've already written. Instead of clearing the list each time and then re-inflating the correct number of views, you could re-use existing views (and only create new ones if there aren't enough) and remove the extra views if there are too many.
int i = 0;
// reuse existing views
for (; i < elements.size() && i < itemInRecycler.getChildCount(); i++) {
Element element = elements.get(i);
CustomView view = (CustomView) itemInRecycler.getChildAt(i);
view.setDisplayData(element);
}
// create new views if there weren't enough to reuse
for (; i < elements.size(); i++) {
Element element = elements.get(i);
CustomView view = inflate();
view.setDisplayData(element);
itemInRecycler.addView(view);
}
// remove any extras after we've reused everything
int viewsToRemove = itemInRecycler.getChildCount() - i;
if (viewsToRemove > 0) {
itemInRecycler.removeViews(i, viewsToRemove);
}
In a world where the elements collection always has between five and seven elements, say, this approach will make sure that you're always re-using the first five CustomViews, and then only inflating or removing 0-2 extra CustomViews.
Related
Currently I want to do something like this
FrameLayout parent = ...
for (childView in parent)
{
if (satisfyPredicate(childView))
{
removeFromParent(childView);
}
}
Unfortunately, I can't seem to figure out how to do this in android (since I am mutating the list as I go)
Requirements:
Cannot remove all views and reinflate all views (views contains pictures and such an algorithm would cause the UI to flicker oddly)
First off- if you want to walk a list and mutate it, you can easily. Just use a do, while, or old style for loop from i=0 to parent.getChildCount()
instead of a foreach loop or an iterator.
Secondly- you probably don't want to remove the views, especially if you may see them later. You probably want to set their visibility to GONE (GONE views are not drawn to the screen and do not take up space, but will remain in the view hierarchy).
I have a for loop within a for loop within a for loop (3 for loops). Each for loop loads at least one view in them some load more than 1. All of the views(textviews, imageviews) are loaded into a relative layout or a linear layout and those layouts are all loaded into one linear layout and all of that is in a scrollview.
I know confusing and probably the worst way to do this. I have looked up different things most of them are listview related such as the endless adapter or lazy loading. I don't think listview will work for what i am trying to do. I have memory problems doing it this way.
So I guess what my question is will ListView be the right direction to go? Will i still be able to use my for-for-for loops?
Consider each block to represent a layout (each of layout consist of textviews, only the black boxes have imageviews and textviews) and also consider each color to represent a for loop. The black borders represent the linear layout that all of these views and other layouts get shoved into. Keep in mind it's not always going to be the same amount of black boxes beneath the red and blue boxes
i don't know if i really got what you want do here but in my opinion u should use an Adapter.
Doing this with layouts as you stated cause memory problems because you are loading a complex hierarchy of views, android is drawing all the views (even the ones that are not visible yet) and none of your views are reused.
Using a ListView and defining different types of AdapterView you should be able to do what you need.
For instance lets say each red box is one AdapterView. So from your scheme you'll have 2 AdapterView, lets call them "ViewOneBlueTwoBlack" and "ViewOneBlueThreeBlack". Also lets say you have more types of AdapterViews "ViewTwoBlueTwoBlack", "ViewTwoBlueFiveBlack", etc...
Now what you need to do is handle in your Adapter the conditions to know when each type of AdapterView should be load.
Or even better if you consider that the redboxes are sections and then the blue boxes become your AdapterViews.
You can find a nice tutorial on ListViews and Adapter here : http://www.vogella.com/tutorials/AndroidListView/article.html
Also your scheme looks a lot like a ExpandableListView check it out just in case.
You should definitly used a ListView and an adpater.
You can have diffenet view type in your ListView. To do so you should have an adapter like this :
public class YourAdapter extends BaseAdapter {
private static int HEADER_TYPE = 0;
private static int CONTENT_TYPE_1 = 1;
private static int CONTENT_TYPE_2 = 2;
#Override
public int getViewTypeCount() {
return 3;
}
#Override
public int getItemViewType(int position) {
if (header)
return HEADER_TYPE;
else if (content_type_1)
return CONTENT_TYPE_1;
else
return CONTENT_TYPE_2;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (getItemViewType(position) == HEADER_TYPE) {
//make header view
} else if (getItemViewType(position) == CONTENT_TYPE_1 {
//make content view
//be careful position is the position in the list view
} else {
//make content view
//be careful position is the position in the list view
return convertView;
}
}
You should be careful when accessing your datas in getView, the postion is the position in the ListView.
I would suggest you to use a table layout for this. Declare a table layout in your xml file, and add views dynamically.
I gave a similar answer here, you can customize the solution to have only one view in row and align accordingly.
"So I guess what my question is will ListView be the right direction to go? Will i still be able to use my for-for-for loops?"
Answer is, right direction depends upon your scenario if your objective can be achieved with both then see which method has least views or widgets in your case there are many layouts but if you use ListView there might be one. Bt in developing the best way is any way which fullfil customer's requirements..
I'm currently having problems with a load of thumbnails in my android application. The thumbnails are displayed in list views and I'm wondering if there is a way for me to know which list items are currently in use and stored for faster scrolling, so that I can recycle unused thumbnails if I get a out of memory response from the BitmapFactory.
To be more clear when loading an image into an image view this is really simple but is there a way to observed which items are currently in use or even better a method that is called in a list view when an item is thrown out?
Thanks in advance.
The standard ListView dynamically creates the views of only those items that will be visible. Even if you have more items in your list than fit on the screen there will be no more views than needed to show the items on the screen.
To list those items that are currently visible on the screen use ListView.getChildAt() and iterate from 0 to ListView.getChildCount(). I.E. do something like this:
for (int i = 0; i < listView.getChildCount(); i += 1) {
// the view itself
View itemView = listView.getChildAt(i);
// the data it is representing (I used String just as an example)
String itemData = (String) listView.getItemAtPosition(
i + listView.getFirstVisiblePosition());
// do whatever you need to do with it
}
I don't know an easy way to get notified for views that are being recycled. Perhaps you can extend the ListView class and override some methods. Perhaps someone already built a component that has what you need.
Use ViewHolder pattern or RecyclerView
I have a ListView in my app that is used to show a list with 2 types of items. The way it is currently implemented is that I have two different XML layouts for each of the item types, my adapter correctly reports the type and in the getView() method I inflate the appropriate XML according the the type in the specified position.
The problem is that in the vast majority of cases the structure of the list of items is that most of the type 1 items are in the beginning and most of the type 2 items are in the end, so usually at first you see mostly type 1 items, you scroll down and at some point you start seeing type 2 items, and they continue until the end of the list.
All works fine while I scroll until I hit that midpoint. Around that point all the calls to getView() get null passed as the convertView parameter. This makes sense obviously. The problem is that is seems like ListView stores all the previous type 1 views in the recycler, and I will not use them as long as I keep scrolling down since from now on most of the views will be type 2 views.
The views are pretty complex, with custom background and bitmaps on top of it, so I end up with lots of views in memory that I will probably never use.
My question is twofold:
Should I even worry about it? right now I am not in the point where I get OOM exceptions, but will I ever get there or is ListView smart enough to "let go" of some of those views when resources get tight?
If I do need to worry about it, is there a way to explicitly tell ListView to clear up it's recycler, or even disable it somehow?
A possible solution is to use the same XML for both layouts, have two ViewGroups in there and just set the visibility of one of them to GONE, but it seems like a waste to have a fairly complex view hierarchy if I am never going to show it.
Should I even worry about it?
No, as the user is perfectly capable of scrolling up, thereby returning to type 1 rows.
right now I am not in the point where I get OOM exceptions, but will I ever get there or is ListView smart enough to "let go" of some of those views when resources get tight?
Once you start getting OutOfMemoryError messages, this ListView will not be your problem. You only have so many row View structures, and all should be really cheap from a memory consumption standpoint.
One suggestion to deal with two different type of child view in Adapter is using getViewTypeCount method and let the adapter know actually you use two different type of view.
The listView maintains each recycler per each view type (in your case, the number will be 2), so you don't worry to any OOM exceptions and don't need to tell ListView to clear up it's recycler.
For more detailed description,
Check: getViewTypeCount and getItemViewType methods of ArrayAdapter
Code snippet for implementation:
public class SampleAdapter extends ArrayAdapter<String> {
...
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
//the result must be in the range 0 to getViewTypeCount() - 1.
if( position < 10 )
return 0;
else
return 1;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
switch( getItemViewType(position) ){
case 0:
//do something for type1 view.
break;
case 1:
//do something for type2 view.
break;
}
return convertView;
}
}
I would not worry too much when having only 2 view types.
If you want to optimize it, I suggest not having a very complex layouts and instead use custom View and do drawing of the Bitmaps yourself. A bit more complex task, but will bring better UX when going through midpoint.
When a list is shown and scrolled around, ListAdapter.getView() either creates a new list item view, or retools an existing view passed to it (as "convertView) to display new content.
I'm wondering, if there's a way to get all the views created by getView()? E.g. if the list item views needs to free some resource when the activity is destroyed, it would be nice to go through all list item views and tell them to release the resource.
Well, it's not as simple as iterating through the ListView using ListView#getChildAt(int) - the ListView is an AbsListView subclass - and AbsListView implements the RecycleBin used to hold Views that will be reused.
Before a View is placed in the RecycleBin its detached from its parent (the list view) using ViewGroup#detachViewFromParent(View), thus rendering it unaccessible using getChildAt(int).
What you should implement is the RecyclerListener interface that will notify you when a View is moved to the RecycleBin.
Since ListView is a ViewGroup, you should be able to iterate on child views like this:
for (int i=0; i < myListView.getChildCount(); i++) {
View v = myListView.getChildAt(i);
//Check to see if this view has the resource, then release it
}
Something similar was needed in this question: Android: Access child views from a ListView
It is java. You can't order the application or instance to be really destroyed so as to free resources. With finalize() you can only rise the probability that java machine will do it.
Unless your adapter is used outside of this Activity (in which case, you don't really know that you no longer need these views right?), your only reference(s) to this adapter is within the Activity. That means that if your Activity is garbage collected your adapter will be garbage collected, which means that your views will get collected. I wouldn't bother trying to optimize this unless you have a measurable problem with this behavior.