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
Related
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)
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.
Documents say:
When the content for your layout is dynamic or not pre-determined, you
can use a layout that subclasses AdapterView to populate the layout
with views at runtime. A subclass of the AdapterView class uses an
Adapter to bind data to its layout.
But most of tutorials are in about ListView,GridView,Spinner and Gallery.
I'm looking to extend a subclass directly from AdapterView. I have to create a custom view that it's content is dependent to an adapter.
How can I do this, and what methods must be overridden?
First, you should be absolutely sure that AdapterView is what you want, because not all "dynamic or not pre-determined" views can be implement via AdapterView. Sometimes you'd better create your view extending ViewGroup.
if you want to use AdapterView, take a look at this really nice example. There are a lot of custom views with adapter on GitHub. Check out this one (extends ViewGroup).
This may not be a total answer to your question but i am showing you most probably a starting point or pointer which can guide you:
Sony Developer Tutorials - 3D ListView
ListView extends AbsListView which in turn extends AdapterView<ListAdapter>. So if you absolutely must implement such a custom view from scratch, you could have a look at the source code of those classes:
ListView
AbsListView
AdapterView
But beware, that's quite a task. Perhaps it may be sufficient to use one of the existing classes and tweak the look.
Deriving from AdapterView can work, but it may not be as beneficial as you hope. Some of the infrastructure provided by AdapterView is package-private, meaning we don't have access to it.
For example, AdapterView manages the selected item index for AbsListView and ListView. However, because methods like setNextSelectedPositionInt(int position) (which is the only path to setting mNextSelectedPosition) are package-private, we can't get to them. AbsListView and ListView can get to them because they're in the same package, but we can't.
(If you dig into the AdapterView source you'll find that setNextSelectedPositionInt() is called from handleDataChanged(). Unfortunately handleDataChanged() is also package-private and is _not_called from anywhere else within AdapterView that could be leveraged to enable setting position.)
That means that if you need to manage selected position, you'll need to recreate that infrastructure in your derived class (or you'll need to derive from ListView or AbsListView...though I suspect you'll run into similar problems deriving from AbsListView). It also means that any AdapterView functionality that revolves around item selection likely won't be fully operational.
You could create something like this :
public class SampleAdapter extends BaseAdapter {
public SampleAdapter() {
// Some constructor
}
public int getCount() {
return count; // Could also be a constant. This indicates the # of times the getView gets invoked.
}
public Object getItem(int position) {
return position; // Returns the position of the current item in the iteration
}
public long getItemId(int position) {
return GridView.INVALID_ROW_ID;
}
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
view = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.some_layout, null);
view.setLayoutParams(new GridView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
view.setBackgroungColor(Color.RED);
return view;
}
}
And this could be invoked like :
GridView sampleView = (GridView) linearLayout.findViewById(R.id.sample_layout);
sampleView.setAdapter(new SampleAdapter());
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!
This question already has answers here:
Android ListView Refresh Single Row
(7 answers)
Closed 8 years ago.
I'm wondering if it is possible to rerender just one element in a listview? I assume by calling notifyDatasetChanged() is gonna rerender the whole list?
Thanks,
you can't render (refresh) a single row, but instead you can get the requested view and make chages on it directly by calling yourListView.getChildAt(int VisiblePosition); where the visiblePostion is the position in the ListView minus yourListView.getFirstVisiblePosition()
Like this :
View v = listViewItems.getChildAt(position -
listViewItems.getFirstVisiblePosition());
v.setBackgroundColor(Color.GREEN);
I hope this helps...
You can, but it's a bit convoluted. You would have to get the index of the first visible item in the list and then use that do decide how how far down in the list of visual items the item is that needs updated, then grab its view and update it there.
It's much easier to just call notifyDatasetChanged().
Also you can use this:
myListView.invalidateViews();
dataAdapter.remove(dataAdapter.getItem(clickedpos));
dataAdapter.insert(t.getText().toString(), clickedpos);
This is how I did it:
Your items (rows) must have unique ids so you can update them later. Set the tag of every view when the list is getting the view from adapter. (You can also use key tag if the default tag is used somewhere else)
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = super.getView(position, convertView, parent);
view.setTag(getItemId(position));
return view;
}
For the update check every element of list, if a view with given id is there it's visible and update must be performed on it.
private void update(long id)
{
int c = list.getChildCount();
for (int i = 0; i < c; i++)
{
View view = list.getChildAt(i);
if ((Long)view.getTag() == id)
{
// update view
}
}
}
It's actually easier than other methods and better when you dealing with ids not positions! Also you must consider scenario when your view get invisible and visible again.
You need to keep track of your adapter (or custom adapter if you are set on fancy features). When you change the data for an item, simply change the fields you are interested in , in your adapter.
Then call notifyDatasetChanged , and the changes will be reflected in your listview.
Note that this approach works exactly the same for Gallery Views as well.