I create ListView and generate its content with BaseAdapter. The ListView contains Button and hidden ImageView for each row. I put OnClickListener on Button to show ImageView.
Scenario:
ListView has 9 rows, and 3 rows visible on screen.
And then I click Button on first row.
Problem:
ImageView on first row shown successfully, but also on fourth and sixth row.
It happens also when I click Button on second row. ImageView on fifth and seventh row is shown.
Question:
Why is that happens? and how to solve it?
This is the code:
UI Thread
.....
adapter = new ContentAdapter(context, content);
listView.setListAdapter(adapter);
.....
ContentAdapter
.....
public View getView(int position, View convertView, ViewGroup parent) {
.....
final ImageView image = (ImageView)convertView.findViewById(R.id.image);
((Button)convertView.findViewById(R.id.button)).setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View view) {
image.setVisibility(View.VISIBLE);
}
});
.....
}
.....
do you check for the view if null or something
if(view == null)
view = ((Activity)YourActivity.get()).getLayoutInflater().inflate(R.layout.list_item, null);
The ListView handles memory, i.e. it recycles views, it does not create a new view (vonverView in you case) each time, but instead it uses a previously generated view (which is convertView).
So when you scroll you are seeing (for example) row1's view on your newly created row. this is why you see that the image is already shown.
To avoid that you must set your image's visibility to View.INVISIBLE.
public View getView(int position, View convertView, ViewGroup parent) {
.....
final ImageView image = (ImageView)convertView.findViewById(R.id.image);
image.setVisibility(View.INVISIBLE);
((Button)convertView.findViewById(R.id.button)).setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View view) {
image.setVisibility(View.VISIBLE);
}
});
.....
}
With that, you will not see a visible image when you scroll, ALTHOUGH, if you make row1's image visible, and you scroll down, when you scroll up again you will find row1's image invisible too, in this case you have to save the state of images somewhere (ArrayList for example, or a Map<Integer position, Boolean isvisible>).
With that being said, you also have to check ViewHolder Pattern for adapters, for better memory consumption.
Hope this helps
Related
I have the following code inside my adapter:
public View getView(int position, View convertView, ViewGroup parent)
{
if (convertView==null)
{
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.box_afisare, null);
}
final TextView titlu = (TextView) convertView.findViewById(R.id.titlu);
titlu.setText(Html.fromHtml(textt.get(position)));
titlu.setTextSize(TypedValue.COMPLEX_UNIT_SP,font);
final Integer pos = position;
titlu.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
if (main_contextt.selectie.contains(id_post.get(pos)))
{
Toast.makeText(mContext," REMOVE ",Toast.LENGTH_LONG).show();
titlu.setBackgroundColor(Color.parseColor("#0077CC"));
}
else
{
main_contextt.selectie.add(id_post.get(pos));
titlu.setBackgroundColor(Color.parseColor("#404040"));
}
}
});
return convertView;
}
I manage to colorate the selected line or lines. But when i scroll the listview and those selected lines are no longer in view range of the phone....the background color disapear.
It disapear only if that line/lines is out of view. I think the adapter is redrawing?
What to do to remain the color set on the line/lines even after i scroll the listview?
thanks
ListViews recycle their child views. So if you have 20 items in your ListView but only enough room on the screen to show 4 at a time, the ListView will only have 4 children which it will recycle to show all 20 items. When one view scrolls out of view, it is recycled for the next view coming into view. See this answer for a great explanation.
What you need to do is tie the color to some underlying data. Then you would use code like.
titlu.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
// set data.get(pos).color
}
});
titlu.titlu.setBackgroundColor(data.get(pos).color);
...something like that.
I have listview with hundred of items. Every item had a couple of LinearLayouts but ONE of them is Visibility.GONE! Every item has textviews and an image. On Image Click i want to set the LinearLayout with visibility.Gone to View.VISIBLE. It works fine until you scroll down the listview, then every 4th item has the same layout set to VISIBLE but i only need the Clicked one! Here is the getView method:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ImageView imgForClick;
View vi = convertView;
if (convertView == null)
vi = inflater.inflate(R.layout.custom_row, null);
final LinearLayout hiddenLayout = (LinearLayout)vi.findViewById(R.id.hiddenLayout);
imgForClick = (ImageView)vi.findViewById(R.id.imageView3);
imgForClick.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
hiddenLayout.setVisibility(View.VISIBLE);
}
});
return vi;
}
That's because you are recycling the views, so the layout gets changed on a click and then that layout is used in your other rows to save memory.
You need to remember the state of each of the rows to know whether or not the layout should be visible or not
Have something like this:
public View getView(final int position, View convertView, ViewGroup parent) {
...
if (shouldBeVisible.get(position)) {
hiddenLayout.setVisibility(View.VISIBLE);
} else {
hiddenLayout.setVisibility(View.GONE);
}
That way the layout will always be set one way or another.
shouldBeVisible is a List of something that lets you know which rows should have that layout visible or not.
EDIT--
An alternative is to remove view recycling, however this will dramatically hurt performance and should NOT be done, but I'm just explaining to list all your options.
You would remove the line
if (convertView == null)
Making Android always inflate a new view, instead of using the recycled one when possible.
I have two custom ListViews. One of them consists of an ImageView and TextViews in each item. The other one consists of a Gallery and a TextView. When I click ImageView I want a bigger version of that picture to appear on the screen. For the gallery the when I click an item of that Gallery, a Gallery will appear on the screen with bigger versions of the pictures.
I can get the position of the clicked item in ListView with onItemClickedListener. However, I cannot understand either TextView or ImageView clicked. How can I do this?
In public View getView(int position, View convertView, ViewGroup parent) { of your CoustomAdapter
TextView tv=convertView.findViewById(R.id.yourTexView);
tv.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO What ever you want
}
});
I want to make ListView with images. Something like this.
But some of items have text only and don't have images. For those items I want to show text only.
How can I do that?
Each item in a list view is a separate view, which can be created as needed. Simply inflate a view from two separate layouts in GetView method of your adapter class.
Something like this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
return new MyViewItem(getImage(), getText(), parent);
}
*****
class MyViewItem extends LinearLayout
{
public MyViewItem(ImageClass image, String text, ViewGroup parent) {
super(parent);
View.inflate(parent, image==null?R.layout.layout1:R.layout.layout2, this);
//Now assign correct text and image using this.findViewById() function.
}
}
I leave it to you to define layout1 and layout2 as well as how to handle actual image and text, but this should do the trick.
You could also have 1 view, but just set visiblity to GONE on the image when you dont want the image to be there. As long as you've set up your layout appropriately, the text should pop over to the edge again just fine!
As the title says I want to know the exact position of the item when I click on a view that is inside the item.
Suppose I have the following code within the getView() method from ArrayAdapter:
...
holder = new ViewHolder ();
holder.iconAction = (ImageView)convertView.findViewById (R.id.download_item_iconAction);
holder.iconAction.setOnClickListener (new View.OnClickListener(){
#Override
public void onClick (View v){
//Item X is clicked
}
});
...
Within onClick() I know the view that is clicked, v, but I don't know the position of the item.
Let's do a trick. I'm going to save the position in a ViewHolder when getView() creates the view:
public View getView (int position, View convertView, ViewGroup parent){
ViewHolder holder;
if (convertView == null){
holder = new ViewHolder ();
holder.iconAction = (ImageView)convertView.findViewById (id);
holder.iconAction.setOnClickListener (new View.OnClickListener(){
#Override
public void onClick (View v){
int pos = (Integer)v.getTag ();
}
});
holder.iconWait.setTag (position);
...
}else{
holder = (ViewHolder)convertView.getTag ();
}
...
}
This code works... but not always. If you have to scroll the list to see all the items the views are recycled. Suppose that the list has 10 elements and only 5 are visible (visible means: if we see 1 pixel line of an item, then this item is visible). Now, if we scroll down we will see the sixth element but the first (0) is still visible. We scroll a little more and the first element will be hidden and we will see that the seventh element appears, BUT the view of this new element is the view of the first element (0). So I'm saving the positions: 0, 1, 2, 3, 4 and 5. The seventh element (6) will have saved the position 0: Wrong.
Another way to get a click callback is using the ListView's OnItemClickListener listener:
listView = getListView ();
listView.setOnItemClickListener (new AdapterView.OnItemClickListener(){
#Override
public void onItemClick (AdapterView<?> parentView, View childView, int position, long id){
...
}
});
If I scroll down I get the exact position, but with this way I receive callbacks when the item is clicked, no matter the clicked child view.
Thanks.
You're very nearly there, all you need to do is set the tag after your if/else clause. This is to make sure the tag is updated when the view is recycled as well as when it is created from new.
e.g
if (convertView == null){
holder = new ViewHolder ();
...
}else{
holder = (ViewHolder)convertView.getTag ();
}
holder.iconWait.setTag (position);
Local anonymous classes can reference final local variables.
Make position final by changing your getView() method to read:
public View getView (final int position, View convertView, ViewGroup parent) {
and then you can reference position from within the OnClickListener:
holder.iconAction.setOnClickListener (new View.OnClickListener(){
#Override
public void onClick (View v){
... use position here ...
}
});
Your second answer (setting a tag on an element) would work fine if you moved holder.iconWait.setTag (position) outside of the if/then statement -- that is, you should set the tag on recycled rows too, not just on newly-inflated ones.
I also needed to add other data to pass to onClick and it worked with strings too.
holder.iconWait.setTag ("String data here");