i have read about the issue of getView called multiple times and all the answers. However, i don't find a solution for my problem.
I have a list where rows have two states: read or not. Well, i want to the items seen for first time have a different color and when i scroll the list, they change their color to "read state".
In order to do this, in the method getView of my adapter i set a field isRead when the row for that item is painted. But the problem is the following: since the method getView is called multiple times the field is marked as read and when the list is shown in the screen it appears as if it had already been read.
Any idea to fix this problem?
Thanks
I assume you mean the issue of getView requesting the same view several times.
ListView does this because it needs to get measurements for the views for different reasons (scrollbar size, layout, etc)
This issue can usually be avoided by not using the "wrap_content" property on your listview.
Other than that, using getView to determine if a view has been displayed is simply a bad idea. ListView has many optimizations that mess with the order getView is called on for each row, so there is no way to know what will happen and your app will start showing odd behavior.
Try to avoid any relationship between the view and the data other than the concept of view as a display of that data.
Instead, have some worker thread or event listener in your listactivity watch the list for which items in the list have been displayed to the user, update the data, and call dataSetChanged on your adaptor.
I had the same problem and I had no reference at all to "wrap_content" in the layouts attirbute. Although this is an old thread I couldn't figured it out how to solve the issue. Thus, I mitigated it by adding a List in the Adapter that holds the positions already drawn, as shown in the code below. I think that it is not the right away of doing that, but it worked for me.
public class ImageAdapter extends BaseAdapter {
private Context mContext;
private List<Integer> usedPositions = new ArrayList<Integer>();
public ImageAdapter(Context c, List<String> imageUrls) {
mContext = c;
...
}
...
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
} else {
imageView = (ImageView) convertView;
}
if (!usedPositions.contains(position)) {
// Your code to fill the imageView object content
usedPositions.add(position); // holds the used position
}
return imageView;
}
}
Not the way you want it to work. The reason that getView() is called multiple times is to allow the OS to measure the rows so it knows how to lay them out. You would need to have it marked as read when they click it or check a box or something.
Here is a very simple way of avoiding the double call when you know nothing below this block of code will distort the layout.
private static List<String> usedPositions = new ArrayList<String>();
...
#Override
public View getView(int position, View rowView, ViewGroup parent) {
rowView = inflater.inflate(R.layout.download_listview_item, null);
Log.d(LOG_TAG, "--> Position: " + position);
if (!usedPositions.contains(String.valueOf(position))){
usedPositions.add(String.valueOf(position));
return rowView;
}else{
usedPositions.remove(String.valueOf(position));
}
...
Related
I'm trying to make an activity able to capture up to 4 images for sending them to our server.
I know how to capture the image and to add them to the activity, this already works, in a non efficient nor elegant way, and I would like to improve that.
Right now, I have Button with an onClick method that attaches an image to an empty ImageView, and and keeps track of how many images have been attached, because I can delete an image in order to pick a new one.
I'm wondering the best strategy, code-wise for future changes.
Options that I have considered but I have not (yet) implemented:
Button adds the image to a GridView so I can add and remove images from it's adapter instead.
The ImageView can attach and remove image from itself by onClick and therefore remove the Button
Any suggestions, ideas, strategies, thoughts?
Thank you in advance!
EDIT 1: my first approach to improve the code
I've implemented a GridView with a custom adapter (GridImageAdapter)
public class GridImagesAdapter extends BaseAdapter{
private List<Bitmap> images = new ArrayList<Bitmap>();
private int img_height;
public GridImagesAdapter(Context context, List<Bitmap> imagenes){
this.imagenes = imagenes;
inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
img_height = (int) (metrics.density * Constants.ONE_ROW_IMG_HEIGHT);
}
/*
* other common methods
*/
#Override
public View getView(int position, View row, ViewGroup parent) {
ViewHolder holder;
if(row == null){
row = inflater.inflate(R.layout.grid_img_item, parent, false);
holder = new ViewHolder();
holder.image = (ImageView) row.findViewById(R.id.img_add_in_grid);
holder.image.setLayoutParams(new GridView.LayoutParams(LayoutParams.MATCH_PARENT, img_height));
holder.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
}else{
holder = (ViewHolder)row.getTag();
}
holder.image.setImageBitmap(getItem(position));
row.setTag(holder);
return row;
}
private class ViewHolder{
ImageView image;
}
}
So I can populate my GridView in the activity like:
private void populateGridView(){
Bitmap a;
a = BitmapFactory.decodeResource(getResources(), R.drawable.no_image);
images.add(a);
images.add(a);
images.add(a);
images.add(a);
GridImagesAdapter adapter = new GridImagesAdapter(this, images);
mGridView.setAdapter(adapter);
mGridView.setOnItemClickListener(new GetImage());
}
where new GetImage() is an OnItemClickListener who takes care of the image capture itself, and replaces one of the R.drawable.no_image Bitmap
(no relevant code in there, just showing a Dialog to the user in order to choose from camera or gallery and start such Activity, and then in the onActivityResult method where I have a Bitmap to work with is where I handle the adapter change)
My question is more about the strategy chosen here than the actual code itself.
Drawbacks? Any more elegant or proper way to achieve the same result?
Everything is welcome, thank you for your answers.
Have u create a gridview and set up the adapter class?
Because I have implemented a similar case. Items are added to listview dynamically (by user input), and there is a button in each row that deletes that particular row (also from the database).
If u have, Let me know from where shall we begin.
Your general idea is more or less correct. You mentioned GetImage is irrelevant, but it absolutely isn't. From I understood, you are changing the view directly in there. Instead you should manipulate the adapter, changing the data it's holding, and then call notifyDataSetChanged. The adapter will take care of notifying the gridview that the data changed and the view will be updated. I would probably use an ArrayAdapter instead of a BaseAdapter as well.
I'm new to Android programing. I need to create a list of 1000 goods for users to click and check or the one they want to buy. I've created the array list and added it to my custom adapter and I have also added it to my list view. my problem is how to get the position for each item selected and I need clarification on the getView and the ViewHolder. I'm not working with toast
See the below tutorials,
http://sunil-android.blogspot.in/2013/04/android-listview-checkbox-example.html
http://developerandro.blogspot.in/2013/09/listview-with-checkbox-android-example.html
http://aboutyusata.blogspot.in/2013/11/how-to-make-listview-with-checkbox-in.html
Hope it helps.
You have to use ListView getCheckedItemPositions()
/**
* Returns the set of checked items in the list. The result is only valid if
* the choice mode has not been set to {#link #CHOICE_MODE_NONE}.
*
* #return A SparseBooleanArray which will return true for each call to
* get(int position) where position is a checked position in the
* list and false otherwise, or <code>null</code> if the choice
* mode is set to {#link #CHOICE_MODE_NONE}.
*/
public SparseBooleanArray getCheckedItemPositions() {
if (mChoiceMode != CHOICE_MODE_NONE) {
return mCheckStates;
}
return null;
}
getView() method is called when new list item that is adapted is being shown on your screen. That is why you need to take good care of the memory and setTag() for every item you inflate. Then when old item is viewed again you will not render it like a new one but get it by the tag you submitted for that item.
Example: If you have 1000 items only couple of those will be shown on the screen and your program will call getView() for those items that are visible plus for couple of items bellow those that are visible so you don't see the lag in inflation while scrolling.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if(view==null)
{
LayoutInflater inflater=getLayoutInflater();
view=inflater.inflate(R.layout.list, parent, false);
TextView textView=(TextView)view.findViewById(R.id.text_hint);
ImageView imageView=(ImageView)view.findViewById(R.id.img);
// here we will setTag() our view later
}
//... (here we will manage the views and set some click listeners)
Now, what is a viewHolder?
The ViewHolder basically is the private class inside your adapter that is used to keep you inflated layout elements together and to let you manipulate those view after you got the using findViewById(int resId).
Some code:
private class ViewHolder{
public ImageView imageView;
public TextView textView;
public ViewHolder(ImageView imageView,TextView textView) {
this.imageView = imageView;
this.textView = textView;
}
}
Now to setTag() as promised.
view.setTag(new Holder(imageView,textView));
Now with this in mind you can get your ViewHolder using the code above and views that you got in the first part of my code. (this is what we will write instead of 3 dots)
ViewHolder h = (Holder) view.getTag();
h.textView.setText(ar[position]);
int resID = getResources().getIdentifier(pic[position], "drawable", getPackageName());
h.imageView.setImageResource(resID);
// here I did set some source to my imageView that is in my layout, but
// you can do whatever you want with these views. And that is what I'm
// going to explain later in this text.
return view;
}
Okay, what next?
After getting the ViewHolder from tag you have your entire layout as a View that can get his OnClickListener easily. Or just a checkBox can get OnCheckedChangeListener.
Inside methods for this listeners you can send the data to you controller (in case you are using MVC model) or to your activity that hosts this View, where you can save the state of the checkbox and title of the item that has been clicked.
For example you can do something like this on your corresponding listener method:
(MainActivity)context.markAsChecked(String title);
But in this case you will also need to have the opposite method for unchecking
(MainActivity)context.markAsUnchecked(String title);
and you will have to handle this in your MainActivity properly by browsing through the array of data that has been selected.
The second solution is to have :
(MainActivity)context.toggleState(String title);
And to handle both events checked and unchecked.
Your method in your Activity would need to do something similar to this:
public void toggleState(String title){
if (data.contains(title))
data.remove(title);
else data.add(title);
}
Then after your user checks what he wants you will have all the checked elements in your data array that in this case is ArrayList. You can also you HashMap for this if you like or something else too.
Hope this helps.
I will be more than happy to answer all of your questions if have some more.
Maybe controller implementation is something that you would like to consider in this case. That would mean that you would be using MVC model for better control of your app and data, and to delegate the tasks to responsible classes and methods. Not to put everything in one Activity :)
Bye
I have a base adapter class which i use to fill in a listview. Some of the contents are defined in a layout file and i also need to dynamically add in a certain number of image button depending on the int value passed to the base adapter.
The obj is a object that has the int value along with a arrayList of bitmaps;
when i run this code i get more image buttons then the value of obj.value.
likePre_pics is the name of the arrayList of bitmaps
Can someone please help?
public class News_Feed_BaseAdapter extends BaseAdapter{
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
LinearLayout linLayout =
(LinearLayout)convertView.findViewById(R.id.like_preview_LinearLayout);
for(int i=0; i< obj.value;i++)
{
ImageButton op= new ImageButton(context);
LayoutParams lpView = new LayoutParams(100, 100);
//op.setImageBitmap(obj.get(position).likePre_pics.get(i));
linLayout.addView(op,lpView);
}
}
}
As i can see from your code there is two problem,first is IndexOutOfBoundsException. ArrayList IndexOutOfBoundsException only occurs when there is more/less item then you are referring to.
//op.setImageBitmap(obj.get(position).likePre_pics.get(i));
The line you commented out. You are referring to obj.get(position) where obj has obj.size() number of element.
Next is ImageButton issue since you are not reusing the convertview in an efficient way when you are adding new imagebutton into linLayout of convertview that is why linLayout is showing more then 2 imagebutton.
For example: If you have 20 items in your list that means getView will be called 20 times. As you will find in android documentation convertView is the old view that gets passed for you to reuse and every-time it gets passed to you , you are adding more imagebutton into it. That's why imagebutton problems are occurring.
Take a look into Romain Guy's World of ListView google i/o presentation if you are interested.
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 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.