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.
Related
As a little eperiment, I'm trying to do the following.
I have an AXML describing a vertical linear layout which contains a listview (only filling 200dp of the vertical linear layout ). The AXML is inflated when the activity starts with SetContentView. Then the listview is correctly populated with values using its Adapter.
In the GetView method of the listview Adapter, I am trying to also dynamically create a button and add it to the linear layout, but for some reason the button is not added.
If I try to add the button in the constructor method of the Adapter instead, it is correctly added.
Can you tell me what could be possibly going wrong?
Let me add some code:
class TracksAdapter : BaseAdapter<string> {
Activity context;
List<Dictionary<string,string>> trackList;
// constructor
public TracksAdapter (Activity context, List<Dictionary<string,string>> trackList) {
this.context = context;
this.trackList = trackList;
// Just as a little test, if I create the button from here it will be correctly added to linear layout:
var ll = context.FindViewById<LinearLayout>(Resource.Id.linLayForResultsActivity);
Button b1 = new Button(context);
b1.Text = "Btn";
ll.AddView(b1);
}
public override View GetView(int position, View oldView, ViewGroup parent) {
// if I create the button from here it will not be added to the layout
var ll = context.FindViewById<LinearLayout>(Resource.Id.linLayForResultsActivity);
Button b1 = new Button(context);
b1.Text = "Btn";
ll.AddView(b1);
// this other code is working
View view = context.LayoutInflater.Inflate(Resource.Layout.ResultItem, null);
var artistLabel = view.FindViewById<TextView>(Resource.Id.resultArtistNameTextView);
artistLabel.Text = trackList[position]["trackArtistName"];
return view;
}
}
Update: adding some more context information because I know this can be a bit weird to understand without it:
In GetView, I don't need to return the new button I am trying to create there. GetView only need to return a listview view item, but, along its execution, GetView also has to create and add a button to the linear layout containing the listview.
The real code is much more complex than that. I have simplified it in the question. In the real code, the listview items are made of text and a button. The GetView also attaches event handlers to the buttons. Then what I need is, when a user clicks a button in any of the listview items, another button is added below the listview. So I need the code for adding another button to be in GetView, and the button needs to be added outside of the listview, ie. to the linear layout containing the listview.
Use the LayoutInflator to create a view based on your layout template, and then inject it into the view where you need it.
LayoutInflater vi = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = vi.inflate(R.layout.your_layout, null);
// fill in any details dynamically here
TextView textView = (TextView) v.findViewById(R.id.a_text_view);
textView.setText("your text");
// insert into main view
ViewGroup insertPoint = (ViewGroup) findViewById(R.id.insert_point);
insertPoint.addView(v, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
I looked in you code, you are returning view, while you add the button to ll, you should return ll
what you return in getView() is what you see in the list item layout, since you're adding the button to ll and returning view, the button won't appear.
you can add the button to view as you implementation
Also check this:
Try using boolean addViewInLayout (View child, int index, ViewGroup.LayoutParams params)
http://developer.android.com/reference/android/view/ViewGroup.html#addViewInLayout(android.view.View, int, android.view.ViewGroup.LayoutParams)
It's working... Without making any changes now it's working as it should... ! Ugh!
I really don't know what I was doing wrong here... probably it was because of some sort of caching of older version of the installed APK.. ? I know this sort of stuff can happen, and that's why I've always been uninstalling the app before deplyoing the new version to the device... but still...!
I have a ListView whose rows are formatted by me. Each row has a mix of ImageView and TextView.
I have also implemented my own adapter and am able to draw each row through it.
Now, I would want something like this-
User clicks on an ImageView (not anywhere else on the row, but only this ImageView should respond to clicks)
I get to know the position of the row whose ImageView was clicked.
I have tried many things for this and have wanted my code to be as efficient as possible (in terms of overkill).
Currently i can capture the click event on that particular ImageView only, but I can't know which row was clicked.
I have provided an attribute in the Row XML like this-
<ImageView android:id="#+id/user_image"
android:padding="5dip"
android:layout_height="60dip"
android:layout_width="60dip"
android:clickable="true"
android:onClick="uImgClickHandler"/>
And in my code, I have a method like this:
public void uImgClickHandler(View v){
Log.d("IMG CLICKED", ""+v.getId());
LinearLayout parentRow = (LinearLayout)v.getParent();
}
I can get the parent row (perhaps) but am not sure how to go further from here.
Can someone please help?
Please refer this,
Me just writing the code to give you idea, Not in correct format
class youaddaper extends BaseAdapter{
public View getView(int position, View convertView, ViewGroup parent){
LayoutInflater inflate = LayoutInflater.from(context);
View v = inflate.inflate(id, parent, false);
ImageView imageview = (ImageView) v.findViewById(R.id.imageView);
imageview.setOnClickListener(new imageViewClickListener(position));
//you can pass what ever to this class you want,
//i mean, you can use array(postion) as per the logic you need to implement
}
class imageViewClickListener implements OnClickListener {
int position;
public imageViewClickListener( int pos)
{
this.position = pos;
}
public void onClick(View v) {
{// you can write the code what happens for the that click and
// you will get the selected row index in position
}
}
}
Hope it helped you
Another option is to use the methods setTag() and getTag() of the view. You set it in your getView like this:
imageView.setTag(new Integer(position));
Then in the onClick() you can find the tag by:
Integer tag = v.getTag();
This will then be used to correlate the image view to the position of the listview item.
Note that this approach will give problems if the listview can lose items from the middle, so that the item positions change during the lifetime of the listview.
you can simply do like this:
in the getview method of our adapter
Button btn1 = (Button) convertView.findViewById(R.id.btn1);
btn1.setOnClickListener(mActivity);
further you can handle the onclick event in your activity,,
for the context of the activity here mActivity just pass the this in the constructer of the adapter and cast it here into the activity like
MyActivity mActivity=(MyActivity)context;
in the adapter.
thanx
This appears to work in a ListActivity whose item layout contains an ImageView with android:onClick="editImage":
public void editImage(View v) {
int[] loc = new int[2];
v.getLocationInWindow(loc);
int pos = getListView().pointToPosition(loc[0], loc[1]);
Cursor c = (Cursor) adapter.getItem(pos);
// c now points at the data row corresponding to the clicked row
}
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));
}
...
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.
I have an application that will have 5-15 buttons depending on what is available from a backend. How do I define the proper GridView layout files to include an array of buttons that will each have different text and other attributes? Each button will essentially add an item to a cart, so the onClick code will be the same except for the item it adds to the cart.
How can I define an array so I can add a variable number of buttons, but still reference each of them by a unique ID? I've seen examples of the arrays.xml, but they have created an array of strings that are pre-set. I need a way to create an object and not have the text defined in the layout or arrays xml file.
Update - Added info about adding to a GridView
I want to add this to a GridView, so calling the [addView method](http://developer.android.com/reference/android/widget/AdapterView.html#addView(android.view.View,%20int) results in an UnsupportedOperationException. I can do the following:
ImageButton b2 = new ImageButton(getApplicationContext());
b2.setBackgroundResource(R.drawable.img_3);
android.widget.LinearLayout container = (android.widget.LinearLayout) findViewById(R.id.lay);
container.addView(b2);
but that doesn't layout the buttons in a grid like I would like. Can this be done in a GridView?
In the following code, you should change the upper limits of the for to a variable.
public class MainActivity
extends Activity
implements View.OnClickListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TableLayout layout = new TableLayout (this);
layout.setLayoutParams( new TableLayout.LayoutParams(4,5) );
layout.setPadding(1,1,1,1);
for (int f=0; f<=13; f++) {
TableRow tr = new TableRow(this);
for (int c=0; c<=9; c++) {
Button b = new Button (this);
b.setText(""+f+c);
b.setTextSize(10.0f);
b.setTextColor(Color.rgb( 100, 200, 200));
b.setOnClickListener(this);
tr.addView(b, 30,30);
} // for
layout.addView(tr);
} // for
super.setContentView(layout);
} // ()
public void onClick(View view) {
((Button) view).setText("*");
((Button) view).setEnabled(false);
}
} // class
Here's a nice sample for you:
https://developer.android.com/guide/topics/ui/layout/gridview.html
You should just create buttons instead of imageviews in getView adapter method.
If you are using a GridView, or a ListView (etc), and are producing Views to populate them via the adapter getView(pos, convertView, viewGroup), you might encounter confusion (i did once).
If you decide to re-use the convertView parameter, you must reset everything inside of it. It is an old view being passed to you by the framework, in order to save the cost of inflating the layout. It is almost never associated with the position it was in the layout before.
class GridAdapter extends BaseAdapter // assigned to your GridView
{
public View getView(int position, View convertView, ViewGroup arg2) {
View view;
if (convertView==null)
{
view = getLayoutInflater().inflate(R.layout.gd_grid_cell, null);
}
else
{
// reusing this view saves inflate cost
// but you really have to restore everything within it to the state you want
view = convertView;
}
return view;
}
// other methods omitted (e.g. getCount, etc)
}
I think this represents one of those Android things where the concept is a little difficult to grasp at first, until you realize there's a significant optimization available within it (have to be nice to CPU on a little mobile device)