In Android, the ListView and RecyclerView reuses view objects when the user scrolls to improve efficiency by not inflating / creating the view objects over and over again.
This is straight forward for any simple lists, but my use case is a bit different. Each item in the list has another list of other items, you can think of it as a list of posts and each post has a list of tags. It can be visualized as follows:
-------------
Post 1
<Tag1> <Tag2> <Tag3>
-------------
Post 2
<Tag4>
-------------
Post 3
<Tag5> <Tag6>
-------------
Since each post has a variable number of tags, it is not as trivial if we want to reuse a set tag view objects across different ListView item.
My current approach to this problem is that there is an implementation of ReusableViewPool which acts as a pool of reusable tag view objects in the above case where each ListView item can get view object from the pool and return unused view object back to the pool. The interface is like:
public interface ReusableViewPool {
View get();
void recycle(View v);
}
In the ListAdapter or RecyclerView.Adapter, it works like:
public View getView(int position, View convertView, ViewGroup parent) {
// return extra tag views to the pool
// add missing tag views from the pool
// update the tag views
}
The approach works fine, but I think this is quite troublesome for a common problem.
So the question is, what approach will you guys take to solve the problem? Do you have any elegant solution in mind?
Thanks.
I have used this technique in the past, I think it's a valid design.
In your case, I might consider a different approach: having a single TextView and making a single SpannableString containing all the tag names, marked up to give the proper look. A lot of it depends on your implementation, though:
Layout/Overflow -- does the tag section have a sort of flow layout or do you have a horizontal scroll to keep all the tags on a single line?
Graphics -- do you have icons interspersed between the names? Do they have different colors or shapes depending on the tag type?
Typography -- do the tag names have different styles or colors depending on the tag type?
If you update your question with some details and preferable some images/screenshots, I can add some sample code.
Related
I am already familiar with the basics and am pretty close to what I want to achieve. I want to have two different layouts in total for the rows but don't want it to be a set pattern of alternation. There could be any pattern for the two row layouts.
My question is how do I use two different xml layouts depending on the information that is going to be displayed? Or if possible, use a single layout and work with visibility for simplicity (current implementation).
Is it possible to send another parameter into getViewItemType()?
There is no way you can set another parameter into getItemViewType (int position) as it only takes one parameter and returns the view that is created by getView(int position, View convertView, ViewGroup parent) on a specific item.
I wolud recommend you using the setVisibility() on a specofic xml Layout which you don't want to display.
Or you can just maintain a small SQliteDatabase for storing what all the items you want to make it visible and call the adapter according to that value in the database.
Hope this helps.
In android adapter any time you can call "getItem()" and with help of that you can decide which layout to show.
Don't forget to override getItem() in your adapter class.
Here is the problem i am facing. I am developing a custom list view in which a single list view item is consisted of multiple text and image views. But depending on the item properties it sometimes needs to have 4 TextViews sometimes 1, sometimes 6.... the number is dynamic and i am having trouble figuring out how to developed a proper view holder pattern that will work.
I can not solve the problem by creating a number of TextViews and hiding them and displaying when necessary, since i dont know how many of them are necessary per item.
And inflating them dynamically and adding to the layout every time in getView is disastrous to say the least :(
Is it a viable solution to have an array list of Views in the holder and then work something out?
I'd recommend using the adapters view types: getItemViewType() and getViewTypeCount()
The number of view types depends on how many permutations are needed to represent all the varying ways an individual list item will look.
So if there are 4 different UI looks, then you'd have 4 different view types. You'd then have a ViewHolder representing each type. Then in the getView(), you just detect which type is represented by the position and inflate/populate as needed. You won't need to worry about hiding different portions of the UI to change up the look. The adapter will ensure you are only given a recycled view for the proper type.
For a nice detailed explanation of using the view type, check out this post.
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.
I tried to add these views to list view using this kind of factory but everytime I try and add the view to a ListActivity, it comes up with nothing. What am I doing wrong? I set my list views like so:
List<View> views = new ArrayList<View>();
for(int x =0;x<tagg_views.size();x++){
lv.addHeaderView(views.get(x));
}
It looks like you are trying to add x number of headers to your ListView. That doesn't make sense.
A ListView should contain x number of copies of the same view, with different information on each line.
Hello ListView gives a good example of the correct usage of a ListView.
Why are you adding the Views to the list yourself? I would highly recommend using any kind of apropriate Adapter for the List. The adapter will handle the creating and recycling of views while the user is scrolling etc. If you use an Adapter it is discouraged to save references to the view yourself like you are doing it in the views list.
The addHeaderView method you are using is made to one single header to the list that always will appear on the top of the list. This means calling it in a loop will not have a reasonable result.
Look into the helloListView example Mayra mentions to understand how a list in android is working. To see how a custom listadapter works have a look at this tutorial looks promising despite the bad code formatting.
A ListView is linked with and Adapter. The Adapter is responsible for the data displayed in the ListView. Take into account that internally ListView creates a pool of itmes (or a pool for each type of item that can be displayed in your case).
For this purpose your adapter needs to implement the following methods:
int getItemViewType(int position): Get the type of View that will be created by getView(int, View, ViewGroup) for the specified item. So you need to identify you types.
int getViewTypeCount(): Returns the number of types of Views that will be created by getView(int, View, ViewGroup). This is used to create a pool for each type of item.