http://developer.android.com/design/media/lists_main.png
As you can see above, I would like to create the 3-line list.
I have looked everywhere but I can't seem to find any documentation on how to do it.
How do I write my adapter for this kind of ListView?
Do I have to extend ListActivity, include a ListView in my .xml layout, or both?
Can somebody provide further insight into it? I'll be very grateful...
You create an XML-Layout for your list-item (row) with three TextViews in a vertical LinearLayout. Then you need to subclass the ArrayAdapter to fill the TextViews. In the getView you fill the lines.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.listitem, null);
}
TextView text1 = (TextView) convertView.findViewById(R.id.textfield1);
if (text1 != null) {
text1.setText(contentArray.get(position).valueForField1);
}
// same for the other fields
return v;
}
Related
Am getting the following warning in Eclipse:
"Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling"
The code which i had used is:
class myadapter extends ArrayAdapter<String>
{
Context context;
int[] images;
String[] mytitle;
String[] mydescp;
myadapter(Context c, String[] tittle, int[] imgs, String[] desc)
{
super(c, R.layout.single_row, R.id.listView1, tittle);
this.context=c;
this.images=imgs;
this.mytitle= tittle;
this.mydescp=desc;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
LayoutInflater inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View row = inflator.inflate(R.layout.single_row, parent, false);
ImageView myImage = (ImageView) row.findViewById(R.id.imageView1);
TextView myText = (TextView) row.findViewById(R.id.textView1);
TextView mydesc = (TextView) row.findViewById(R.id.textView2);
myImage.setImageResource(images[position]);
myText.setText(mytitle[position]);
mydesc.setText(mydescp[position]);
return row;
}
}
Am getting warning in the line : View row = inflator.inflate(R.layout.single_row, parent, false);
And it causes my android application to Force Close... What can i do it now??
Any Suggestions???
You need to recycle your views.What android as a system cares about is only the items that are visible.So you have to recycle the row items which are out of focus to be re-used for the newitems.
Or else imagine the amount of caching involved.
#Override
public View getView(int position, View row, ViewGroup parent) {
// TODO Auto-generated method stub
if(row==null){
LayoutInflater inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflator.inflate(R.layout.single_row, parent, false);
}
ImageView myImage = (ImageView) row.findViewById(R.id.imageView1);
TextView myText = (TextView) row.findViewById(R.id.textView1);
TextView mydesc = (TextView) row.findViewById(R.id.textView2);
myImage.setImageResource(images[position]);
myText.setText(mytitle[position]);
mydesc.setText(mydescp[position]);
return row;
}
You should re-use the view instead of inflating again and again. This brings down performance.
From your code,
View row = inflator.inflate(R.layout.single_row, parent, false);
this will inflate everytime when you scroll. To maximize the performance, use it like
//re-use
if (row == null)
{
inflate code here
}
else
{
you already have a view `row`, just use it.
}
You can see, for the first time row will be null & it will inflate and store it in View row. But, from the next time, it's not going to inflate again and again instead it will use the View row. (Re-use)
"Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling"
It's not the error it's just the warning for asking you to use ViewHolder Pattern. Let me explain you why it's important.
Without ViewHolder Pattern :
The first time it was loaded, convertView is null. We’ll have to inflate our list item layout and find the TextView via findViewById().
The second time it was loaded, convertView is not null, good! We don’t have to inflate it again. But we’ll use findViewById() again.
The following times it was loaded, convertView is definitely not null. But findViewById() is constantly called, it will work but, it slows down the performance especially if you have lots of items and Views in your ListView.
With the ViewHolder Design Pattern :
The first time it was loaded, convertView is null. We’ll have to inflate our list item layout, instantiate the ViewHolder, find the TextView via findViewById() and assign it to the ViewHolder, and set the ViewHolder as tag of convertView.
The second time it was loaded, convertView is not null, good! We don’t have to inflate it again. And here’s the sweet thing, we won’t have to call findViewById() since we can now access the TextView via its ViewHolder.
The following time it was loaded, convertView is definitely not null. The findViewById() is never called again, and that makes our smooth ListView scrolling.
Why to use?
Your code might call findViewById() frequently during the scrolling of ListView, which can slow down performance. Even when the Adapter returns an inflated view for recycling, you still need to look up the elements and update them. A way around repeated use of findViewById() is to use the view holder design pattern.
So, what is ViewHolder?
A ViewHolder object stores each of the component views inside the tag field of the Layout, so you can immediately access them without the need to look them up repeatedly. First, you need to create a class to hold your exact set of views.
How to use?
Make a separate class as ViewHolder & declare what you use like EditText,TextView etc..
static class ViewHolder {
TextView text;
TextView timestamp;
ImageView icon;
ProgressBar progress;
int position;
}
Then populate the ViewHolder and store it inside the layout.
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) { // if convertView is null
convertView = mInflater.inflate(R.layout.mylayout,
parent, false);
holder = new ViewHolder();
// initialize views
convertView.setTag(holder); // set tag on view
} else {
holder = (ViewHolder) convertView.getTag();
// if not null get tag
// no need to initialize
}
//update views here
return convertView;
}
Source :
Making ListView Scrolling Smooth from Android documentation
Android ViewHolder Pattern example
Hi Vinesh Senthilvel ,
Don't worry
Use my code below , It will definetely solve your problem,
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
LayoutInflater inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View row = inflator.inflate(R.layout.single_row, null, false);
ImageView myImage = (ImageView) row.findViewById(R.id.imageView1);
TextView myText = (TextView) row.findViewById(R.id.textView1);
TextView mydesc = (TextView) row.findViewById(R.id.textView2);
myImage.setImageResource(images[position]);
myText.setText(mytitle[position]);
mydesc.setText(mydescp[position]);
return row;
}
If still problem persists then post logcat exception stack trace ,I will help you
There is a another approach , You just have to import android.view.LayoutInflater; and take the context of parent (ViewGroup) - parent.getContext() ,It will work
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
View v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.single_row, parent, false);
You have to use this code
View row = convertView;
before this line,
View row = inflator.inflate(R.layout.single_row, parent, false);
Hope it works..
i have a ListView with a onClicklListener.
The ListView has a row Layout of say /res/listitem_a
now after an onClickevent of the any listitem , i want to change the layout of
only that listitem to say /res/listitem_b..
any help on how shall i proceed.
Use BaseAdapter and modify in getView call.
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.custom_layout, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// Change text size
holder.text.setTextAppearance(context,R.style.customStyle);
return convertView;
}
static class ViewHolder {
TextView text;
}
And you can use position variable in getView call to change specific row. Hope this help!!!
You can use ViewFlipper as layout of the rows. With ViewFlipper you can specify as many layouts as you want and flip among them when something happen (like a click event). Here is a good tutorial about ViewFlipper.
Moreover, you should implement a custom adapter, extending BaseAdapter, and overriding the getView method.
#Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.your_row_layout, null); //this will inflate the layout into each row
}
//from here on, assign the information to display to the layout widgets
Hope I've helped you.
I'm new to android programming and doing the first steps with Adapters (for a ListView).
Overriding the Adapter.getView I often see things like this:
public View getView(int position, View convertView, ViewGroup parent) {
View itemView = null;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) parent.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
itemView = inflater.inflate(R.layout.table_row, null);
} else {
itemView = convertView;
}
// play with itemView
return itemView;
}
My question is what speaks against this:
public View getView(int position, View convertView, ViewGroup parent) {
View itemView = super(position, convertView, parent);
// play with itemView
return itemView;
}
This seems to work for me but I'm sure there's a big point I'm missing :D
Thanks for reading and sorry for my bad english ...
You can use
View itemView = super(position, convertView, parent);
if only you are deriving from "ready to use" adapters (not BaseAdapter), like SimpleAdapter, or ArrayAdapter, as they already have their implementation for the getView().
Have a look at them: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.4_r2.1/android/widget/ArrayAdapter.java#361 for the ArrayAdapter, and
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.4_r2.1/android/widget/SimpleAdapter.java#113 for SimpleAdapter.
If you derive from BaseAdapter, you will have to manualy implement the whole method, as you've described in the first example, because it does not have it out of the box: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.4_r2.1/android/widget/BaseAdapter.java#BaseAdapter
The getView(..)-method of the Adapter can be multiple ways. The only question is, which one is the most efficient?
An interesting article to read and make you understand the ListView more detailed: http://lucasr.org/2012/04/05/performance-tips-for-androids-listview/
If you mean that this piece of code:
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) parent.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
itemView = inflater.inflate(R.layout.table_row, null);
} else {
itemView = convertView;
}
seems unnecessary for you: this piece of code allows Android to create a relatively small number of cells (equals to the number of cells that are visible on your screen +-), and then "recycle" these cells - use them over and over again while the user scrolls the list, instead of creating a cell for each object in your array.
This will help you with:
Saving memory - because you don't create view for each element in your array
Saving CPU usage - creating a view object out of xml file ("inflating") is relatively expensive task and doing so for each item in your array might choke your UI thread
I have an Activity, which simply consists in listing Pair<String, String> objects. I have a custom TextWithSubTextAdapter, which extends ArrayAdapter:
public View getView(int position, View convertView, ViewGroup parent)
{
View view;
if (convertView == null)
{
LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = li.inflate(R.layout.text_sub, null);
TextView tv = (TextView)view.findViewById(R.id.mainText);
tv.setText(mCategories.get(position).first);
TextView desc = (TextView)view.findViewById(R.id.subText);
desc.setText(Html.fromHtml(mCategories.get(position).second));
}
else
{
view = (View) convertView;
}
return view;
}
mCategories is an ArrayList<Pair<String, String>>
I then call lv.setAdapter(new TextSubTextAdapter(this, Common.physConstants));
As long as I have a limited set of elements, It works great, because I don't need to scroll. However, when I add enough elements, after scrolling, the items swap their positions, like this:
I suspect that this behavior is due to me calling mCategories.get(position). Because the Views are never kept in the background and Android regenerates them every time, I never get the same item, as position will rarely have the same value.
Is there a way to get a constant id, which could allow me to get items with fixed positions ? I tried to use getItemID, but I do not understand how to implement it.
Note: Every string comes from a strings.xml file. They are never compared, and instanciated once, at startup.
When you scroll your list Android dynamically reuses the Views which scroll out of the screen. These convertViews don't have the content which should be at this position yet. You have to set that manually.
View view;
if (convertView == null)
{
LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = li.inflate(R.layout.text_sub, null);
}
else
{
view = convertView;
}
TextView tv = (TextView)view.findViewById(R.id.mainText);
tv.setText(mCategories.get(position).first);
TextView desc = (TextView)view.findViewById(R.id.subText);
desc.setText(Html.fromHtml(mCategories.get(position).second));
return view;
I have a ListView, and I have added a header (with getListView().addHeaderView) that simply contains a TextEdit widget.
Then when I tap the TextEdit to start writting, the keyboard appears and it messes up the list!
If I tap everywhere else to hide the keyboard, the list messes up again!
I don't know why is this happening. I thought it was something related with the onConfigurationChanged method, but after implementing it (and adding the corresponding attribute in the manifest file) the problem persists.
How could I fix it? Why is Android messing up my list?
EDIT:
My list uses a custom adapter, this is the getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v != null) {
return v;
}
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.list_row, null);
ListTask list_item = items.get(position);
if (list_item != null) {
TextView item_name = (TextView) v.findViewById(R.id.item_name);
item_name.setText(list_item.getTitle());
}
return v;
}
The problem is not the value of my items, but their order. They are displayed in a different order when the keyboard appears, but the values are correct.
EDIT2:
Ok, I have changed my getView method with rekaszeru's suggestion and now it works as expected. But now I'm facing another problem: what if my items have two textviews?
Let's say the second textview is optional, and "Item 1" and "Item 3" have it, but "Item 2" does not, so it's initialized as a void String (length == 0).
The first time the list is displayed, it shows "Item1" and "Item 3" with their second textview, and "Item 2" without it. That's correct. But when the keyboard appears, the "Item 2" takes the second textview of another item and displays it!
This is the modified code I have right now:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.list_row, null);
}
ListTask list_item = items.get(position);
TextView item_name = (TextView) convertView.findViewById(R.id.item_name);
TextView item_optional_text = (TextView) convertView.findViewById(R.id.item_optional_text);
item_name.setText(list_item.getTitle());
// if the item has defined the optional text, make some room and display it
if (item_optional_text.isNotEmpty()) {
LayoutParams layout_params = (LayoutParams) item_name.getLayoutParams();
layout_params.topMargin = 10;
layout_params.height = -2; // -2: wrap_content
item_name.setLayoutParams(layout_params);
item_optional_text.setText(list_item.getOptionalText());
}
return convertView;
}
The isNotEmpty() does this in the Item class:
public boolean isNotEmpty() {
return this.optional_text.length() > 0;
}
Maybe it's too complex to understand in a written question. If so, I can make a short video showing the problem and my source code. Thanks in advance for your help guys.
Your row recycling is messed up. Android is not changing the order of the items, you are.
Right now, if you are passed a row to recycle, you return it without modification. This is a mistake. You are supposed to modify the contents of the row to reflect the data at the supplied position. The only piece of logic you can skip in this case is inflating a brand-new row.
Here is a free excerpt from one of my books that goes through all of this.
You should override the getView method in your ListAdapter implementation, and make sure that you always assign a new value to the view that you are returning (or at least always update it to contain the proper data).
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
convertView = inflater.inflate(R.layout.list_row, parent, false);
//set the necessary data in your TextViews, Checkboxes, etc...
return convertView;
}
If you don't inflate your item renderer, then you can instantiate it from code, like:
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
convertView = new TextView([...]);
convertView.setText(textBasedOnYourData);
return convertView;
}
Edit
As #CommonsWare noted, attention should be payed to the recycling of your list item renderer. So instead of instantiating it every time, you should check whether it already exists or not, and update the underlying TextView afterwards.
So I'd suggest give a try to this slightly modified getView implementation:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.list_row, null);
}
ListTask list_item = items.get(position);
TextView item_name = (TextView) convertView.findViewById(R.id.item_name);
//the item should never be null, but just in case:
item_name.setText((list_item == null) ? "" : list_item.getTitle());
return convertView;
}