I wonder what is convert view ? I understand that it is
The old view to reuse, if possible. Note: You should check that this
view is non-null and of an appropriate type before using. If it is not
possible to convert this view to display the correct data, this method
can create a new view. Heterogeneous lists can specify their number of
view types, so that this View is always of the right type (see
getViewTypeCount() and getItemViewType(int)).
What is this under the hood ?
Is it just simple view inflated earlier ? Or something other ?
Why we cannot use it like this ?
if (convertView!=null) {
return convertView;
}
else {
return new ......
}
Why we have to use setTag method to set reference to the our inflated view ?
convertView.setTag(holder);
We are setting one view another view reference as the TAG , why not just to pass it directly as convertView ?
Please help with this question, I can't write any code because cannot understand this.
Under the hood speaking you would see that it inflates the views just a couple of times (enough to fill up the screen) as this is really slow. It is as you said reusing the views it had inflated earlier. When you scroll, it just tells you that you need to fill the earlier inflated views with the data appropriate for the current position.
You actually can always return a new view you inflate every time, but this will have a negative impact on your performance and there by scrolling smoothness.
With the setTag you are telling it to remember the object which holds information about the view itself (called ViewHolder).
What is this under the hood ? Is it just simple view inflated earlier
? Or something other ?
it is both. In particular is an array of views of the type you inflated. The size of this array is equal to the number of views enough to fill completely your screen. This way you allocate only a constant number of views.
Why we cannot use it like this ?
if (convertView!=null) {
return convertView;
}
else {
return new ......
}
you still need to fill up your view with the data of your dataset. In the snippet you posted above you aren't doing it. You could do something like
if (convertView!=null) {
// convertView.findVIewById(...)
// set data to view
return convertView;
}
else {
convertView = new ......
// convertView.findVIewById(...)
// set data to view
return converView;
}
Why we have to use setTag method to set reference to the our inflated
view ?
it's called ViewHolder pattern, and you are not setting the inflated view, but a small object that contains the inflated's view component. This way you can avoid calling findViewById every time getView is called and you want to fill up the view with content of your dataset (at position)
Related
I have not found anything that explains what the View Type is and it must be used to differentiate between views to display when there are more than one, in which case one would use the viewType passed to onCreateViewHolder:
onCreateViewHolder(ViewGroup parent, int viewType)
While there exists a getItemViewType method there is no setItemViewType method.
So, it appears that the View Type is set by Android, there are only certain types.
What are the native types and their values? I can't find anything that defines those natives in Android's documentation.
How is it intended to define various views, such as if one recyclerview should have the red background? I have created a boolean value to identify an object that should be displayed differently but as onCreateViewHolder doesn't accept anything but the ViewGroup and int, there seems no way to do this, yet obviously others do.
I don't understand View Type at all and would appreciate a good explanation. For example, from the Android API guide for getItemViewType return value:
"integer value identifying the type of the view needed to represent the item at position. Type codes need not be contiguous."
What does that even mean? I haven't found anything that explains where it comes from or how it can be set, or even if it can be set.
There are no native view types.
You define the view types. Let's say you had to different types of items in a RecyclerView. One with a red background and one with a blue background. They alternated based on position. You'd do something like this:
```
/// Inside the RecyclerView.Adapter code:
const VIEW_TYPE_BLUE = 0;
const VIEW_TYPE_RED = 1;
getItemViewType(int position) {
return position % 2;
}
onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType === VIEW_TYPE_BLUE) {
// create blue view.
} else {
// create red view.
}
}
```
This will allow you to do various customizations. It makes it easy for example, if you had a header View at the very top of the RecyclerView.
ViewType is defined by you. Its basically an enumeration. If all of your items have the same view, you can just ignore it and return 1 for getItemViewType. If you have different views for different items, you just have to return a unique value for each view.
What each value is doesn't matter- Android doesn't know or care. It just uses it as a key in a hash lookup, to tell what type of view to recycle
I am extending Android BaseAdapter.
In regards to method
getView (int position, View convertView, ViewGroup parent)
The docs say
You should check that this view is non-null and of an appropriate type
before using. If it is not possible to convert this view to display
the correct data, this method can create a new view.
I am wondering how do I check if the View is of appropriate type?
Lets say I would expect a LinearLayout, with two TextView children? How would I check this properly?
Would this be correct?
if (convertView instanceof LinearLayout) {
if (convertView.findViewById(someid) != null) {
//its what I expect...
}
}
If you only have 1 view type in your adapter, then you only need to check if it is non null.
Checking view types only applies if you if you have multiple view types (when you override getViewTypeCount() to return anything other than 1), then you need to figure out which type it is based on the position. Basically, if you do not override getViewTypeCount() then just check for null.
Creating a ViewHolder class would be appropriate if you have multiple views this can help you with getting the view on the layout during run time as you are using a holder with each view
I have a custom list view which is being popluated via an array adaptor.
Each item/row contains three buttons and some related textViews.
All elements in a row describe the details for a device on the cloud. So data is fetched from the cloud and then the list is populated. No. of rows is equal of the number of devices.
Everything was fine till I added the feature for a periodic update for the items.
The problem is that after each periodic update it over writes the data for a device in the wrong row.
I tried two ways to refresh each row.
I kept a map for (DeviceID and view) and then based on the deviceId
i would get the view and update it. Now,this didn't work as the views are reused and so as i scroll
down, basically the same view is reused as shows the new data. And
so the map entry of the previous device is over written with the new
one.
I tried to directly call getView() and pass the position but that
also didn't work.
I understand that the views are reused so there is no way to know exactly which view is associated with a deviceID.
But could some please help me figure out how to update the correct view with the correct data?
Thanks.
If you are using Holder pattern, then there is a way to do this.
Step 1: Add one attribute i.e. position to Holder.
private class ViewHolder {
....
....
int position;
}
Step 2: Initialise the holder position into getView()
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
...
holder.position = position;
}
....
}
Step 3: Check holder position and view's position values. If both are same then do your task.
if (mHolder.position == mPosition) {
// This is you required row. Do your task.
}
Read Async loading for more details.
I'm creating kind of music player which has a listview with songs (having progress bar near currently playing song)
What is important is that I have an Item with views which can be changed from outside (in some handler)
public View getView(int i, View convertView, ViewGroup viewGroup) {
RelativeLayout result;
if (convertView == null) {
result = (RelativeLayout) inflater.inflate(R.layout.list_item, viewGroup, false);
} else {
result = (RelativeLayout) convertView;
}
...
ProgressBar progressBar = result.findViewById(R.id.progressBar)
...
if (i == currentSong) {
// saving to instance variable
this.currentlyPlayingProgressBar = progressBar;
} else {
progressBar.setVisibility(View.GONE);
}
...
return result;
}
(Code was changed to focus on my problem)
Btw currentSong can be changed from outside, adapter.notifyDataSetChanged() is being called in this case.
It seems like I'm using listView incorrectly, but I don't know the better way.
The main problem is that I need to have links to views to change them.
And the only way where I can get them is in getView method which reuses those view in a way only google developers can explain=(
First problem
This is all happening in Fragment which is just a part of a viewPager. when user scrolls of this fragment and then scrolls back then getView method is being called with some strange objects inside.. And I override currentlyPlayingProgressBar with this invalid value. Which causes the freeze of my statusbar. (it starts updating wrong view)
And I have no idea which view is it..
Second problem
I am reusing list items and this means that when user scrolls list view down - then sometimes he gets actually the same list item with the same progressBar.
This progressBar must be invisible but it's not (I think it's all because of my usage of currentlyPlayingProgressBar from outside)
Thanks in advance.
You can do this in two ways:
1) notifyDataSetChanged, which just resets entire ListView and assigns all visible list items again to views. notifyDataSetChanged is very expensive, since it makes entire list and view hierarchy to be recreated, layouts are measured, etc, which is slow. For frequent update of single list item, such as progress bar change, this is overkill.
2) Find view of particular listview item, and update only that one. We'll focus on this approach.
First you need to somehow identify which list view contains which list item. Common approach is to use View.setTag in your Adapter.getView, where setTag parameter is Object of your choice, may be same item as you return for Adapter.getItem.
So later you know which ListView child view has which item assigned.
Assuming you want to update particular item displayed in ListView, you have to iterate through ListView child views to find which view displays your item:
Object myItem = ...;
for(int i = list.getChildCount(); --i>=0; ){
View v = list.getChildAt(i);
Object tag = v.getTag();
if(tag==myItem) {
// found, rebind this item
bindItemToView(myItem, v);
break;
}
}
You must expect that ListView currently may not display your list item (is scrolled away).
From code above you see that it calls bindItemToView, which is your function to bind item to list view. You'd probably call same function to setup the item view in Adapter.getView.
You may also optimize it further, assuming you want to update only ProgressBar, then don't call bindItemToView, but update only ProgressBar in your listitem view (findViewById, setup values).
Hint: you can make it even more optimal by using ViewHolder approach. Then setTag would not contain your item object, but your ViewHolder object.
#Pointer null has also given very usefull aproach, but in your case I think u are updating the list which is not visible, in this case you have to set the tag from the adapter just like the list index and curresponding check if the list item exist between the last visible item or first visible item then update it else donot update..
I am new to Android development and reading through some example code. I have copied one method from the sample code in an Adapter class (derived from ArrayAdapter), the derived class has a checkbox in addition to the text view:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View listItem = super.getView(position, convertView, parent);
CheckedTextView checkMark = null;
ViewHolder holder = (ViewHolder) listItem.getTag();
if (holder != null) {
checkMark = holder.checkMark;
} else {
checkMark = (CheckedTextView) listItem.findViewById(android.R.id.text1);
holder = new ViewHolder(checkMark);
listItem.setTag(holder);
}
checkMark.setChecked(isInCollection(position));
return listItem;
}
private class ViewHolder {
protected final CheckedTextView checkMark;
public ViewHolder(CheckedTextView checkMark) {
this.checkMark = checkMark;
}
}
The sample code is to optimize the getView by caching the View within a ViewHolder object.
Where I am confused is I thought the convertView, if not null, would be re-purposed and then the View data is populated into it and returned.
If this is the case, then how could the setTag / getTag methods called in the code be relied upon? It would seem that the same object would have to be retrieved in order for it to work?
perhaps view returned from getTag on a subsequent call is for a different list item, and returns the wrong view
Adapters use a RecycleBin. This class allows the ListView to only create as many row layouts as will fit on the screen, plus one or two for scrolling and pre-loading. So if you have a ListView with 1000 rows and a screen that only displays 7 rows, odds are the ListViiew will only have 8 unique Views.
Now to your question using my example above: only eight row layouts and 8 subsequent ViewHolders are ever created. When the users scrolls no new row layouts are ever created; only the content of the row layout changes. So getTag() will always have a valid ViewHolder that references the appropriate View(s).
(Does that help?)
You're on the right track, here's some information that may help make more sense of how ListViews work:
A simple implementation of the getView() method has two goals. The first is inflating the View to be shown on the list. The second is populating the View with the data that needs to be shown.
As you stated, ListViews re-purpose the Views that compose the list. This is sometimes referred to as view recycling. The reason for this is scalability. Consider a ListView that contains the data of 1000 items. Views can take up a lot of space, and it would not be feasible to inflate 1000 Views and keep them all in memory as this could lead to performance hits or the dreaded OutOfMemoryException. In order to keep ListViews lightweight, Android uses the getView() method to marry Views with the underlying data. When the user scrolls up and down the list, any Views that move off the screen are placed in a pool of views to be reused. The convertView parameter of getView() comes from this list. Initially, this pool is empty, so null Views are passed to getView(). Thus, the first part of getView should be checking to see if convertView has been previously inflated. Additionally, you'll want to configure the attributes of convertView that will be common to all list items. That code will look something like this:
if(convertView == null)
{
convertView = new TextView(context);
convertView.setTextSize(28);
convertView.setTextColor(R.color.black);
}
The second part of an implementation of getView() looks at your underlying data source for the list and configures this specific instance of the View. For example, in our test list, we may have an Array of Strings to set the text of the view, and want to set the tag as the current position in the Data of this View. We know which item in the list we're working with based on the position parmeter. This configuration comes next.
String listText = myListStringsArray[position];
((TextView)convertView).setText(listText);
convertView.setTag(position);
This allows us to minimize the amount of time we spend inflating/creating new views, a costly operation, while still being able to quickly configuring each view for display. Putting it all together, your method will look like this:
#Override
public View getView(int position, View convertView, ViewGroup)
{
if(convertView == null)
{
convertView = new TextView(context);
//For more complex views, you may want to inflate this view from a layout file using a LayoutInflator, but I'm going to keep this example simple.
//And now, configure your View, for example...
convertView.setTextSize(28);
convertView.setTextColor(R.color.black);
}
//Configure the View for the item at 'position'
String listText = myListStringsArray[position];
((TextView)convertView).setText(listText);
convertView.setTag(position);
//Finally, we'll return the view to be added to the list.
return convertView;
}
As you can see, a ViewHolder isn't needed because the OS handles it for you! The Views themselves should be considered temporary objects and any information they need to hold onto should be managed with your underlying data.
One further caveat, the OS does nothing to the Views that get placed in the pool, they're as-is, including any data they've been populated with or changes made to them. A well-implemented getView() method will ensure that the underlying data keeps track of any changes in the state of views. For example, if you change text color of your TextView to red onClick, when that view is recycled the text color will remain red. Text color, in this case, should be linked to some underlying data and set outside of the if(convertView == null) conditional each time getView() is called. (Basically, static setup common for all convertViews happens inside the conditional, dynamic setup based on the current list item and user input happens after) Hope this helps!
Edited - Made the example simpler and cleaned up the code, thanks Sam!