I'm trying to do a List that shows an image and a simple text describing the image.
In my search on internet I found many ways to do this. Some people using ArrayAdapter, others using SimpleCursorAdapter. One thing I notice, many people are creating classes inheriting from ListActivity and in the setListAdapter method they are inserting other classes derived from Array or SimpleCursor adapter.
First question: is this the best way to do this?
I created a LinearLayout with a ListView inside. And to insert rows, another layout was created with an ImageView and a TextView.
Second question: is this correct?
I'm confusing about creation of this type of component. Is this the correct way to do this?
Yes, this is correct, although you will need to use a CursorAdapter instead of a SimpleCursorAdapter, since the point of a SimpleCursorAdapter is to populate a row with only a TextView in it.
You will have a getView method on your CursorAdapter that expands your row layout:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) { // we don't have a recycled view
convertView = LayoutInflator.from(getContext()).inflate(
R.layout.row, parent, false);
}
// setup our row
TextView text = (TextView) convertView.findViewById(R.id.text_view);
text.setText( ... );
ImageView image = (ImageView) convertView.findViewById(R.id.image_view);
image.setImageBitmap( ... );
return convertView;
}
When you're setting the text and image of your views, you can use adapter methods like getItem to access the underlying data you need.
Related
I have been debugging my application and i saw that when i was scrolling the listview the method getView() of the class BaseAdapter is called to generate new views
public View getView(int position, View convertView, ViewGroup parent) {
Article article = this.articles.get(position);
return new MainView(this.context, articulo.getTitle() , articles.getDescription(),articles.getImgUrl());) }
when i scroll the listActivity to see the new items this method is invoked again to create the below list view items, as a consequence that the list items have images the ListActivity get slow, is there any way to create all the items view once, and not create ListItems when we are scrolling the listActivity
ListViews are highly optimized for performance, you should use ViewHolder inside your ListAdapter to cache the ListItems.
check http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List14.html
the rule is, first set up your customview, pack everything inside your holder and pin this holder onto the view, the second time the view is used android simple extract the holder information (really fast).
It's probably slowing down because of the number of objects that are created. For performance you should reuse your rows. See the getView implementation here: http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List4.html and http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List14.html
You should not create a new View on each call to getView. The convertView that is being passed in allows use to reuse an existing View. In your case this will be an instance of MainView. So you can do something like this:
MainView mv;
if (convertView != null){
mv = (MainView) convertView;
((TextView) mv.findViewById(R.id.title)).setText(articulo.getTitle());
// similar for description and imgUrl
} else {
mv = new MainView(...);
}
return mv;
In addition, you could use the ViewHolder pattern suggested by Michele. This will allow you to avoid the findViewById lookups when setting title etc. Here is a great explanation of ViewHolder.
I have a list of custom objects that I am loading into an ActivityList that allows multiple selections and displays the checkbox on the right side. Those custom objects contain a field named "enabled". When I load the data I want to scroll through the list of objects and check the checkbox for each row that represents an object that has the enable field marked true. Currently I have all of the records loading into the ActivityList as I want but I can not make any of the rows "checked" even though objects are marked as "enabled".
This is the code I am using to mark a row as checked
for (int i = 0; i < sourceList.length; i++) {
DataSource d = sourceList[i];
view.getChildAt(i).getClass().toString());
CheckedTextView checkView = (CheckedTextView)view.getChildAt(i);
checkView.setChecked(Boolean.parseBoolean(d.enabled));
}
I have put this code directly after calling setListAdapter and I have put it in the onContentChanged() function. However, in both places the rows are not displayed yet so the view.getChildAt(i) returns null so obviously the row does not get checked.
Can anyone tell me where I can put this code so that it will be executed after all rows have been added and displayed on the screen?
Thank you!
Originally I did not have a custom Adapter I was just using ArrayAdapter. In order to override the getView() method I created a custom Adapter and extended ArrayAdapter. I am still allowing the ArrayAdapter class to do most of the work but I am overriding getView(). Here is my code for getView()
#Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = super.getView(position, convertView, parent);
if (convertView.getClass() == CheckedTextView.class){
CheckedTextView checkView = (CheckedTextView)convertView;
DataSource d = getItem(position);
checkView.setText(d.getName());
checkView.setChecked(Boolean.parseBoolean(d.enabled));
}
return convertView;
}
Even with this code none of the check boxes are being checked. The DataSource's name field is being set as the text but the setChecked() method does not seem to be working for me. I also tried hard coding that to be
checkView.setChecked(true);
That did not work for me either. Do you have any more ideas on what I might be doing wrong?
Thanks again!
Can you show your adapter code? The place to call setChecked is in your getView code in your adapter. It will be something like:
#Override
getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(
R.layout.my_list_row, parent, false);
}
CheckedTextView checkView = (CheckedTextView)
convertView.findViewById(R.id.check_view);
DataSource d = sourceList[i];
checkView.setChecked(Boolean.parseBoolean(d.enabled));
return convertView;
}
ListView doesn't really expose its children via getChildAt. The intended interface is via constructing and populating rows in getView.
What you need to do is call myList.setItemChecked(position, Boolean.parseBoolean(d.enabled)); in your getView method (where myList is an instance of ListView).
Also make sure you've called myList.setChoiceMode(int choiceMode) before setting your list adapter.
If you have Checkable views inside another view, then the containing view needs to implement Checkable as well for checkable items in lists to work properly (see CheckableRelativeLayout for instance)
i've got a listView with some data that i inflated to get some nice background color. The problem is that i want to get some awesome separators and i'm unable to setDividerHeight() depending on the row's data, because it seems that i can't inflate two views on the same getView() method, here's some code:
public View getView(int position, View convertView, ViewGroup parent){
String myText = getItem(position).toString();
String firstLetter = Character.toString(myText.charAt(0));
if(convertView == null){
convertView = this.inflater.inflate(R.layout.lettersrows, null);
}
TextView tv = (TextView)convertView.findViewById(R.id.label);
tv.setText(this.list.get(position));
tv.setTextSize(25);
convertView.setBackgroundColor((position & 1) == 1 ? Color.WHITE : Color.LTGRAY);
/**This is what i want to do*/
if(!firstLetter.equals("A")){
convertView = this.inflater.inflate(R.layout.letters, null);
ListView lv = (ListView)convertView.findViewById(R.id.letters_listview);
lv.setDividerHeight(3);
}
return convertView;
}
The error i'm getting is a NullPointerException at: tv.setText(this.list.get(position));
I guess that dues to that the convertView is now a ListView that's why it doesn't find where to set the text. How could i fix this problem.
Best regards.
You can use your custom Adapter to inflate as many different types of layout as you want.
For this, you just need to change your getViewTypeCount method to return the type of different views you want (2 in your example, regular item and separator) and adapt your getView() method to chose the correct view type to display.
Everything is explained in this great tutorial
Note: In the tutorial, they do that by implementing a getItemViewType() method. This can be useful in some cases.
How to do my own custom list? I mean, that each element of list will be looking like I want.
Create a custom list item row layout
You have to create a custom list row item in the layout folder, just like you define the usual activity layouts. There you place your icons, TextViews etc and place them properly.
Override the specific adapter you need
You then need to override the specific adapter you need in order to associate the data from your curso / object list with your layout xml element. This is usually done by overriding the getViewor bindView method of the adapter of your choice (ResourceCursorAdapter, ArrayAdapter,..).
#Override
public View getView(int position, View convertView, ViewGroup parent){
if(convertView == null){
convertView = mInflater.inflate(R.layout.row_item, parent, false);
}
TextView someTextViewOnMyRowLayout = (TextView)findViewById(...);
someTextViewOnMyRowLayout.setText(...);
return convertView;
}
You can create an xml file which acts as an element that looks like you want..
and assign that to the list using inflators and adapters..
Try this..
http://www.softwarepassion.com/android-series-custom-listview-items-and-adapters/ ,
http://www.androidpeople.com/android-custom-listview-tutorial-example/
I have a ListView with custom ArrayAdapter. Each of the row in this ListView has an icon and some text. These icons are downloaded in background,cached and then using a callback, substituted in their respective ImageViews. The logic to get a thumbnail from cache or download is triggered every time getView() runs.
Now, according to Romain Guy:
"there is absolutely no guarantee on
the order in which getView() will be
called nor how many times."
I have seen this happen, for a row of size two getView() was being called six times!
How do I change my code to avoid duplicate thumbnail-fetch-requests and also handle view recycling?
Thanks.
Exactly, that could happen for example when you have
android:layout_height="wrap_content"
in your ListView definition. Changing it to fill_parent/match_parent would avoid it.
From api.
public abstract View getView (int position, View convertView,
ViewGroup parent)
convertView - 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.
So if getView has already been called for this specific index then convertView will be the View object that was returned from that first call.
You can do something like.
if(!(convertView instanceof ImageView)){
convertView = new ImageView();
//get image from whereever
} else {} // ImageView already created
I m experiancing the same issue i change the layout_height of listView to match_parent resolve my issue.
My understanding is that you need to use the ViewHolder design pattern here. Just using a returned convertView can lead to reuse of a previous view (with some other image assigned in this case).
public class ImageAdapter extends ArrayAdapter<String> {
// Image adapter code goes here.
private ViewHolder {
public ImageView imageView;
public String url;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = null;
ViewHolder viewHolder;
String url = getUrl(position);
if (convertView == null) {
// There was no view to recycle. Create a new view.
view = inflator.inflate(R.layout.image_layout, parent, false);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) view.findViewById(R.id.image_view);
viewHolder.url = url;
view.setTag(viewHolder);
} else {
// We got a view that can be recycled.
view = convertView;
viewHolder = ((ViewHolder) view.getTag());
if (viewHolder.url.equals(url)) {
// Nothing to do, we have the view with the correct info already.
return view;
}
}
// Do work to set your imageView which can be accessed by viewHolder.imageView
return view;
}
}
The better would be to create a object with Thumbnail(bitmap) and the text. And read the thumbnail if its not available in the object.
Create an array of ImageView objects in your adapter and cache them as you retrive them (whether from cache or web). For example, in getView, before you fetch the ImageView, check if it's already in your local array, if so, use it, if not fetch, once received store in your local ImageView array for future use.
My Fragment.xml has a ListView, the layout setting of this ListView was android:layout_height="wrap_content", and this ListView will bind to SimpleCursorAdapter later. Then I have the same issue in ViewBinder be called 3 times. The issue resolved after I change the layout_height="wrap_content" to "95p". I do consider that the "wrap_content" height cause this issue.
Trying to modify your Fragment.xml and I guess the 3 times called issue will no longer exist.