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.
Related
I read too many articles about this. I Tried all type of permutation & combination of recyclerview method such as setHasFixedSize(true) ,setNestedScrollingEnabled(false), setItemViewCacheSize(20) etc. But my prediction is the below statement is ture:
The main reason Jank occurs the first time because on the first time it is loading the values onto memory dynamically. while once it is down it already has a few elements pre loaded
When I run my app on android the first time I can see a first scroll is lagging(exactly 5-6 items).Once that first scroll is done the list view is really really fast, I mean faster than anything else.
If I close my app without killing it from background and open it again, It won't lag.
if I "kill" my app and run it again, I get the lag for first 5-6 items.
MY QUESTIONS :
Is there any way to load the values onto memory dynamically before user interaction, Like showing a splash screen and load the layout in background thread? I have tried splash screen but this question is how to load the layout onto memory.
How is the scrolling of youTube,Twitter,Instagram is smooth? How are they load their layout first time onto memory?
Note: Main container is ConstraintLayout with custom background & my hierarchy is totally flat, but 3 buttons have drawable icons and custom background , one material shapable imageview, one slider with custom thumb and custom progress drawable and a frameLayout
Make sure to override:
getItemViewType(…) from docs:
Return the view type of the item at position for the purposes of view
recycling.
The default implementation of this method returns 0, making the
assumption of a single view type for the adapter. Unlike ListView
adapters, types need not be contiguous. Consider using id resources to
uniquely identify item view types.
If you have different view types, return an int that identifies said view types, otherwise no need to override.
getItemId(…) from docs:
Return the stable ID for the item at position. If hasStableIds would
return false this method should return NO_ID. The default
implementation of this method returns NO_ID.
That means the function must return a unique id for every item. Use the id of your objects if there's any, otherwise try with the hashCode:
#Override
public long getItemId(int position) {
return itemList.get(position).hashCode();
}
If implemented properly, use setHasStableIds(true). From docs:
Indicates whether each item in the data set can be represented with a
unique identifier of type java.lang.Long.
getItemCount(…) from docs:
Returns the total number of items in the data set held by the adapter.
Simply the number of items in the list.
#Override
public int getItemCount() {
return itemList.size();
}
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've tried to change the background color of specific items in a ListView.
first, catch it from database:
ListAdapter adapter = new ArrayAdapter(getApplicationContext(),
android.R.layout.simple_list_item_1, db.getAllApps());
final ListView list = (ListView) findViewById(R.id.ListViewApps);
list.setAdapter(adapter);
then I will set all apps in different color, if they have the tag activated
// if app is activated in db --> set another colour in ListView
private void setAppCheck(ListView list) {
List<String> apps = db.getAllApps();
for (int i = 0; i < list.getCount(); i++) {
if (db.appActivated(apps.get(i)).equals("activated")) {
list.setBackgroundColor(0xffaaaaaa); // it changes ALL items...
} else {
// do nothing
}
}
}
And there is Problem, with list.setItemChecked(i, true) I can change it with a specific position, but how do I change the Background color of the specific Item in the ListView?
Hope you can help me.
The cleanest way to do what you're trying to do is writing your own CursorAdapter supporting two view types: activated apps and deactivated apps. Then in your getView method, when you're inflating your views, you can set the background color accordingly.
Having two item types will make the Android framework automatically pass only convert views of the correct type to getView, so the only time you need to check for the type is during creation.
You may find this answer helpful.
Adapter basics
In Android, Adapters are used to translate your data (in your case from an SQLite database) into Views that can be displayed in listviews, spinners, etc. (AdapterView to be specific). One of the most commonly used ones is the CursorAdapter which has basic infrastructure necessary when the associated data is supposed to be read from a cursor.
You will mainly need three methods in your adapter:
- getViewTypeCount which will tell the framework how many types of views your adapter knows. For you this will be two: activated and deactivated apps.
getItemViewType which, when passed a specific position in the data (here: the cursor), is able to decide which of those types that position falls into. For this, you will likely be able to reuse your db.appActivated code, at least in large parts.
getView, which, when passed a position, can turn the data associated with that position into a View for display. Let's look at that last part in more depth.
Android does some very nifty stuff to make sure your app is fast and slick and responsive. One of those things is, it will only keep enough views around for all positions in the list that are displayed. So, if you have a list that can display 10 items at a time, but your data holds a million records, it will still only keep 10 views around (well, actually, a few more from when stuff is scrolled off screen, but definitely not the one million it would require for every data record).
When the time comes to actually turn data into visible representations - getView - it will pass an old, previously visible but now off screen view (a recycled view) in as the convertView parameter for you to try adapting it to display the data that's been requeusted. This is because inflating new views is comparatively more expensive than just taking an existing one and changing its texts or images or whatever. The view types you have told it about will help it to only pass the type of convert view into getView that is appropriate for the position that's been requested.
This way, you need to only inflate a new view if the passed convert view is inappropriate somehow. And inappropriate, in this case, usually only means "if it is null". So, usually, what you end up with is something very close to this:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = // inflate a new view
}
// bind the convert view to the data, i.e. set its text views, images, and - in your case - background color
}
A video says more than a thousand words
You may want to watch this Google I/O keynote for a more comprehensive explanation of how it all ties together.
I learned Android's ArrayAdapter today, and find there is a commom pattern which uses a ViewHolder to hold Views' reference instead of calling findViewById everytime.
But how does it work? Adapter is usually used to display a list of View(Group)s, If I cache the View, why don't they all reference to the oldest one?
If you want the best explanation on how the ViewHolder works, check out Romain Guy's Google I/O 2009 talk in youtube , specially the first 15 minutes.
In short, the Adapter functions as a link between the underlying data and the ViewGroup. It will render as many Views as required to fill the screen. Upon scrolling or any other event that pushes a View is out of the screen, the Adapter will reuse that View, filled with the correct data, to be rendered at the screen.
The getView(int pos, View view, ViewGroup parent) method will use the right View at any time, regardless of your layout. I do not know the internals of this, but I'm sure you can browse the source code for any adapter (such as ArrayAdapter.java) if you're interested.
The ViewHolder just keeps a pointer to the Views as obtained by view.findViewById(int id). It is the Adapter responsibility to return the right data corresponding to any position.
Slides 11 to 13 of Romain's presentation will make it a lot more clear than anything I can write.
Sorry but denis' answer may be wrong.
In fact, the view instances(and ViewHolders) are as many as your screen can display.
If your screen looks like:
[list view]
the first item
the second item
the third item
the fourth item
You will have 4 instances of views. If you scroll screen, the first will disappear but be pass to getItem() as convertView for you to create the fifth item.
So you can use the references in first ViewHolder.
I believe the work beneath the list view is something like this (considering we have only one item view type):
do once:
inflate item view from layout, cache it
repeat for every item:
ask adapter to fill the data into the view
draw the view on the screen
move to next item
so you have the view which is inflated from xml layout and can be reused for drawing multiple list items. ViewHolder speeds it up a bit more by saving getViewById lookups.
Can somebody in plain words explain me the usage of getViewTypeCount() and getItemViewType() methods of ArrayAdapter?
These handle the case where you want different types of view for different rows. For instance, in a contacts application you may want even rows to have pictures on the left side and odd rows to have pictures on the right. In that case, you would use:
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
return position % 2;
}
The framework uses your view type to decide which views to hand you via convertView in your getView method. In other words, in the above example, your even rows will only get recycled views with pictures on the left side to reuse, and odd rows will only get ones with pictures on the right.
If every row in your list has the same layout, you don't need to worry about view types. In fact, BaseAdapter.java provides a default behavior for all adapters:
public int getItemViewType(int position) {
return 0;
}
public int getViewTypeCount() {
return 1;
}
This indeed provides you with the same view type for every row.
Edit - to outline the general flow:
You bind data to your AdapterView using an adapter.
The AdapterView tries to display items that are visible to the user.
The framework calls getItemViewType for row n, the row it is about to display.
The framework checks its recycled views pool for views of row n's type. It doesn't find any because no views have been recycled yet.
getView is called for row n.
You call getItemViewType for row n to determine what type of view you should use.
You use an if/switch statement to inflate a different xml file depending on which view type is required.
You fill the view with information.
You return the view, exiting getView, and your row's view is displayed to the user.
Now, when a view is recycled by scrolling off the screen it goes into a recycled views pool that is managed by the framework. These are essentially organized by view type so that a view of the correct type is given to you in convertView parameter in your getView method:
The framework again calls getItemViewType for the row it wants to display.
This time, there is a view in the recycled pool of the appropriate type.
The recycled view is passed to you as the convertView parameter to your getView method.
You fill the recycled view with new information and return it.
If we need to show different type of view in list-view then its good to use getViewTypeCount() and getItemViewType() in adapter instead of toggling a view View.GONE and View.VISIBLE can be very expensive task inside getView() which will affect the list scroll.
Please check this one for use of getViewTypeCount() and getItemViewType() in Adapter.
Link : the-use-of-getviewtypecount
Watch Outttt!!!!
I had to face for a problem implementing a ListView yesterday and it's two types of views for rows got jumbled just after I scroll it. Even though the top voted answer within this thread gives a good general explanation it hasn't highlighted the most important bit of information to stop the above UI bug which I have mentioned.
Here is my explanation:
Both getViewTypeCount() and getItemViewType() are being used by BaseAdapter's getView method to find out which type of a view should it be fetch, recycled and returned. (as explained in the top answer within the thread). But if you don't implement these two methods intuitively according to the Android API Doc, then you might get into the problem I mentioned about.
Summarized Guideline for the implementation:
To implement multiple types of Views for ListView's rows we have to essentially implement, getItemViewType() and getViewTypeCount() methods. And getItemViewType() documentation gives us a Note as follows:
Note: Integers must be in the range 0 to getViewTypeCount() - 1.
IGNORE_ITEM_VIEW_TYPE can also be returned.
So in your getItemViewType() you should return values for the View Type, starting from 0, to the last type as (number of types - 1). For example, let's say you only have three types of views? So depending on the data object for the view, you could only return 0 or 1 or 2 from the getItemViewType() method, like a zero-based array index. And as you have three types of views used,
your getViewTypeCount() method must return 3.
In any case if you return any other integer values like 1, 2, 3 or 111, 222, 333 for this method you definitely might experience the above UI bug which you just placed by not obeying to the Android API Doc.
If you didn't get the clue or couldn't still resolve and need further information please read my detailed answer within this StackOverflow Q&A thread.
Read the Android Developer Doc for further information you might be find the clue directly.
Hope this answer might be helpful to someone out there to save a lots of hours!!!
Cheers!!!