Android GridView Item Selectors change position and duplicate - android

I have a GridView layout with 3 columns and several rows that contains pictures taken from Facebook. When I select an image, a selector appears for that item in the gridView as normal. (I don't use the android selector but a custom layout linked below.)
The problem is this: if I select one or more items, when I scroll down the screen to see more pictures I discover that other items have already been selected! Moreover, if I scroll up to see the items I've previously selected, the selectors change their position. It seems to me that the index of each selector update every time I scroll the screen.
I created a personal ImageAdapter to inflate the pictures from facebook. Here the getView:
public View getView(final int position, View convertView, ViewGroup parent) {
View v;
ImageView imageView = null;
if(convertView==null){
LayoutInflater li = getLayoutInflater();
v = li.inflate(R.layout.photos_item, null);
}else{
v = convertView;
}
imageView = (ImageView)v.findViewById(R.id.image);
v.setTag(imageView);
if (arrayPhotos.get(position).getPictureSource() != null){
Log.i(TAG, "Position in GridView = " + Integer.valueOf(position).toString());
imageLoader.DisplayImage(arrayPhotos.get(position).getPictureSource(), imageView);
}
return v;
}
Here the onItemClick:
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
View checkedItem;
checkedItem = (View) view.findViewById(R.id.check);
fbPicture = arrayFbPictures.get(position);
Log.i(TAG, "Position = " + arrayFbPictures.indexOf(fbPicture));
Log.i(TAG, "PicId = " + fbPicture.getPictureID());
if( fbPicture.isChoosed() ){
Log.i(TAG, "checkIcon DISACTIVATED");
fbPicture.setChoosed(false);
checkedItem.setVisibility(View.INVISIBLE);
parent.invalidate();
}
else{
Log.i(TAG, "checkIcon ACTIVATED");
fbPicture.setChoosed(true);
checkedItem.setVisibility(View.VISIBLE);
parent.invalidate();
}
}
That is the xml of the GridView:
<GridView
android:id="#+id/gridview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center"
android:gravity="center"
android:numColumns="auto_fit"
android:columnWidth="96dp"
android:stretchMode="spacingWidth"
android:horizontalSpacing="8dp"
android:verticalSpacing="8dp"
android:paddingTop="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp" >
</GridView>
And this the xml of the custom selector:
<RelativeLayout
android:id="#+id/check"
android:layout_width="96dp"
android:layout_height="96dp"
android:background="#color/black_trasparency_light"
android:visibility="invisible" >
<ImageView
android:id="#+id/image_check"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:scaleType="center"
android:src="#drawable/ic_check_colored_48" />
</RelativeLayout>
Thank you.

The reason this is happening is because GridView uses view recycling. When you scroll down the GridView, instead of creating new views to put in the bottom, GridView reuses the ones that disappear from the top. What this means is that you have to remember to fully set the state of the view inside the call to getView().
That's a little hard to digest, so here's some code:
public View getView(final int position, View convertView, ViewGroup parent) {
...
View checkedItem = (View) v.findViewById(R.id.check);
fbPicture = arrayFbPictures.get(position);
if (fbPicture.isChoosed()) {
checkedItem.setVisibility(View.VISIBLE);
} else {
checkedItem.setVisibility(View.INVISIBLE);
}
return v;
}
The code above is setting the checked state of the convertView every time getView() is called, so the recycled view will not retain any of its previous state.
PS. The is done to conserve the memory and to improve performance of GridView and ListView. You can watch this awesome presentation for more information.

Related

SwipeListView stays in "swiped state" after deleting a row

I'm using SwipeListView library for my list. List adapter is using ViewHolder Pattern, everything is working fine. I set up swipe left and there's a back view under my list item view. On this back view I have a text view "Delete". After click, delete operation is being performed. Data in adapter is being refreshed. Row is deleted ..but "swiped state" is being set on a row which has position close to that row which was deleted. I believie it happens because "swiped state" is remembered by convertview. When I comment out checking if convertview is null, problem is gone, but list scroll performance is unacceptable.
Is there any way to clear convertview after delete action? Or is there any other work around?
My ViewHolder:
public static <T extends View> T get(View view, int id) {
SparseArray<View> viewHolder = (SparseArray<View>) view.getTag();
if (viewHolder == null) {
viewHolder = new SparseArray<View>();
view.setTag(viewHolder);
}
View childView = viewHolder.get(id);
if (childView == null) {
childView = view.findViewById(id);
viewHolder.put(id, childView);
}
return (T) childView;
}
My adapter's getView():
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(context)
.inflate(R.layout.level_item, parent, false);
}
...
...
del = ViewHolderNew.get(convertView, R.id.del);
del.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Level level = list.get(position);
long id = level.getId();
Level leveltoDelete = Level.load(Level.class, id);
new Delete().from(Recipient.class).where("level = ?", id).execute();
leveltoDelete.delete();
remove(level);
notifyDataSetInvalidated();
}
});
And my xml file:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="0dp"
android:background="#color/white_background"
>
//item back layout
<LinearLayout
android:id="#+id/item_back"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/del"
android:layout_width="match_parent"
android:layout_height="180dp"
android:gravity="center_vertical|right"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="#color/delete_background"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:paddingRight="30dp"
android:textColor="#color/white_background"
android:text="Delete"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>
//item front layout
<RelativeLayout
android:id="#+id/item_front"
android:layout_width="match_parent"
android:layout_height="match_parent">
...
I don't know that library, but I assume that it changes visibility of row item's children.
After delete and refresh, ListView requests new rows, passing you the old views as convertViews - and one of them is the "swiped" view. Probably the same behaviour you will observe after scrolling down the list, as it's also reuses views not visible anymore.
If you can say that the view is after swipe, probably you could apply reverse transformation before reuse (or just inflate the new one, one row per page shouldnt be very bad for performance).
However, this soultion won't maintain swiped state if you scroll down and go back. If you need to track swiped position, you should consider adding additional view type (see getViewType(position) and getViewTypeCount()).

ListView doesn't accept onClickListener when list item contains ViewPager

I have a list view and inside it's items I have a view pager as you can see in the below code.
While the viewPager is inside the item view List doesn't accept onItemClickListener and when I click Items nothing happens.
One solution that I found was adding onClickListener to ViewPager pages , But I think this is not a good solution because If you list contains 100 Items and each item have a view pager that contains 5 page so I should define 500 Listener Instead of one Listener.
I tried all solutions that I searched for example changing the focusable or clickable attribute of root view.
Please share your suggestions with me.
This is the list item layout :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/propertylist_item_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/purple"
android:paddingBottom="1dp"
android:descendantFocusability="blocksDescendants" >
<!-- background slider -->
<android.support.v4.view.ViewPager
android:id="#+id/property_images_view_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<!-- favorite icon -->
<ImageButton
android:id="#+id/favorite_button"
android:contentDescription="#string/favorite_button_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:background="#android:color/transparent"
android:layout_margin="10dp"
android:src="#drawable/ic_favorite" />
...
<!-- Some other tags -->
This is the list adapter code :
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
if (view == null)
{
LayoutInflater inflater;
inflater = LayoutInflater.from(getContext());
view = inflater.inflate(R.layout.property_listing_item, parent, false);
}
RPropertyStorage property = getItem(position);
if (property != null)
{
PropertyListingImagesAdapter adapter = new PropertyListingImagesAdapter(property.getImages());
ViewPager imagesPager = (ViewPager) view.findViewById(R.id.property_images_view_pager);
imagesPager.requestDisallowInterceptTouchEvent(false);
imagesPager.setAdapter(adapter);
}
return view;
}
OnItemClickListener of ListView :
list.setOnItemClickListener(new OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3)
{
RToast.getInstance().showMessage("Clicked");
RLog.info("Clicked");
}
});
ViewPager has very "strong" code that handle touch events. For me it is only one way to catch viewpager clicks through it's own clicklisteners.
Don't care about
If you list contains 100 Items and each item have a view pager that contains 5 page so I should define 500 Listener Instead of one Listener.
because listview creates only as many view as can display on the screen at the same time + 2; You can check it by such code in your adapter
int count=0;
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
if (view == null)
{
LayoutInflater inflater;
inflater = LayoutInflater.from(getContext());
view = inflater.inflate(R.layout.property_listing_item, parent, false);
Log.v("","creating "+count);
count++;
}
//your code
}
Listener is only links. It is no matter how many links you have on some method. Just check if it is not null.

Changing background color of ListView item upon clicking a Button

Apologies if this question has been asked previously.
I have a ListFragment that contains a Button in each list item. Clicking on the Button should change the background color of that particular list item. I understand that the ListView inside the ListFragment refreshes itself implying that if a user scrolls it is likely that a list item that was not previously clicked will have its background color changed.
Many of the solutions I have come across involve storing the position of the list item that was clicked in an overridden getView(int position, View convertView, ViewGroup parent) method implemented in a custom adapter (that extends SimpleAdapter in my case).
However my problem is compounded by the presence of a Button inside every list item implying that the Button will not be clickable unless I attach an OnClickListener inside a custom adapter (this part is done). Now, trying to store the position of the Button's list item fails because the saved position is always a refreshed position and not the absolute position of the list item. I also tried setting tags for each button but even those get recycled as the page scrolls, which is (unfortunately) expected behaviour considering the design of ListView.
I understand that this problem is easily solvable if the a list item does not have children that need to be clicked. But what if there are children within every list item that need to respond to clicks separate from the parent view that contains each list item ? I have also tried using selectors to manipulate a list item's background but that hasn't helped either.
Here is my Custom Adapter's getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent){
View view = convertView;
final int pos = position;
if(view == null){
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.fragment_layout, null);
Button button = (Button) view.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v) {
Log.d("CLICKED POSITION",Integer.valueOf(pos).toString());
View parent = (View) v.getParent();
parent.setBackgroundColor(Color.GREEN);
}
});
}
return view;
}
And the XML layout file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="300dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_weight="1"
android:descendantFocusability="blocksDescendants"
android:layout_margin="5dp"
>
<ListView android:id="#id/android:list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/label"
android:layout_gravity="start"
/>
<TextView
android:id="#+id/text_id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="end"
/>
<Button
android:id="#+id/button"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="#string/button"
android:layout_margin="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:clickable="true"
/>
</LinearLayout>
I have spent many hours on this problem and I am unable to arrive at a solution. Any direction, guidance or a solution would be most appreciated.
In your Custom Adapter's getView() method, you are inflating the view and attaching the onclicklistener only if the convertView is null. convertView IS the recycled view. If convertView is not null, then you need to change the values in that view. Your getView() method should be like this instead
#Override
public View getView(int position, View convertView, ViewGroup parent){
View view = convertView;
final int pos = position;
//No recycled view, so create a new one
if(view == null){
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.fragment_layout, null);
}
//By this line, we either have a view that is created in the if block above or passed via convertView
Button button = (Button) view.findViewById(R.id.button);
//attach listener to the view, recycled or not
button.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v) {
Log.d("CLICKED POSITION",Integer.valueOf(pos).toString());
//parent not required as we are have the view directly
//View parent = (View) v.getParent();
v.setBackgroundColor(Color.GREEN);
}
});
return view;
}
Indeed you should also be changing any values associated with that item according to that position, if they change. For example, if each button displays the position in the list, you should also add the following line
button.setText("Button " + position);

Adding image to listview after listview item click

I am developing an app which has a listView having 1 textView which displays the content and one imageView. What i want is, when i click the listView i want to set the imageView with the tick mark image which i have. Its working fine. Once i click the listView, the tick mark image is loaded on that listview item on which i clicked.
The problem arises when i scroll. When i scroll, i could see some other listview item down below has a tick mark loaded. not sure how the image was set on that position.
I have read somewhere that when the listView is scrolled, the view is refreshed. Is that causing the problem?
Can anyone help how can iresolve this? I want the image to be shown(loaded) on the listView item on which i clicked and now other listView item.
Below is the xml stating listView items
method_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#android:color/white"
android:text="This is a check box button which reacts upon the check that user clicks. I am testing it with the big string and checking how it looks" />
<ImageView
android:id="#+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/textView1"
android:layout_centerHorizontal="true" />
<ImageView
android:id="#+id/imageView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="#+id/imageView1"
android:layout_alignParentRight="true"
android:layout_marginRight="34dp" />
</RelativeLayout>
Below is the part of class snippet:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.method_list_main);
final ListView list = (ListView)findViewById(R.id.listView1);
adapter=new MethodLazyAdapter(this,ARRAY.preparationSteps,ARRAY.timeToPrepare,ARRAY.arrowValue,noOfSteps,list);
list.setAdapter(adapter);
list.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
// TODO Auto-generated method stub
RelativeLayout ll = (RelativeLayout) view;
ImageView image = (ImageView)ll.findViewById(R.id.imageView2);
if(tick[position]){
tick[position] = false;
image.setImageResource(0);
}
else{
tick[position] = true;
image.setImageResource(R.drawable.select_right);
}
System.out.println("Position is: "+position);
}
});
}
Initially all tick[i] value is set to false.
Below is the getView function of adapter class
public View getView(final int position, View convertView, ViewGroup parent) {
View vi=convertView;
if(convertView==null)
vi = inflater.inflate(R.layout.method_item, null);
TextView text = (TextView)vi.findViewById(R.id.textView1);
text.setText(preparationSteps\[position\]);
return vi;
}
Try this to get what you want
in public area add this variable
public SparseBooleanArray checked = new SparseBooleanArray();
then in onItemClick
list.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
boolean stat = checked.get(position, false);
checked.put(position, !stat);
adapter.setChecked();
System.out.println("Position is: "+position);
}
});
and in Adapter Class
If adapter not sub class in activiy add variable in public area
private SparseBooleanArray checked = new SparseBooleanArray();
and add this method to class
public void setChecked(SparseBooleanArray ch){
checked = ch;
notifyDataSetChanged();
}
Or if it sub class use our cheked variable we defined it up
then in getView method
public View getView(final int position, View convertView, ViewGroup parent) {
View vi=convertView;
if(convertView==null)
vi = inflater.inflate(R.layout.method_item, null);
TextView text = (TextView)vi.findViewById(R.id.textView1);
text.setText(preparationSteps\[position\]);
ImageView image = (ImageView)vi.findViewById(R.id.imageView2);
if(checked.get(position, false)){
image.setImageResource(0);
}
else{
image.setImageResource(R.drawable.select_right);
}
return vi;
}
Please let me know if this help you.
Check out this post:
android - populating ListView with data from JSONArray
the reason for that is bacouse your are using your converted view wrong.
follow the example code on the post i paste.
if you will inflate your row view every time this problem will disappear. but this implementation is not recommended.

display a listview with different formatted entries

I want to build a list view which displays "awards" the player has received in the game.
There are many examples out there of how to display a listview with text + icon.
However, in my case I need to vary the icon.
In the case the award is given fro a one-time achievement. In that case I just want the text and the "Trophy" to appear.
In other cases it is awarded for doing something a specified number of times.
In that case I need to include a progress bar.
How can I do the varying layouts within one list view?
Update:
Thanks to Leyths it is working (sort of).
Thing is that the ProgressBar is not showing - just its spinner.
This is the row:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:id="#+id/tvwShortMessage"
android:typeface="sans"
android:textSize="14sp"
android:textStyle="italic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView android:id="#+id/tvwLongMessage"
android:typeface="sans"
android:textSize="14sp"
android:textStyle="italic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/tvwShortMessage"/>
<ProgressBar
android:id="#+id/pgbAwardProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="#android:style/Widget.ProgressBar.Small"
android:layout_marginRight="5dp"
android:layout_below="#id/tvwLongMessage"/>
</RelativeLayout>
Code:
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater)AwardsActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.awards_row, parent, false);
}
AwardItem award = awards.get(position);
ProgressBar pb = (ProgressBar) v.findViewById(R.id.pgbAwardProgress);
if(award.award_type == PROGRESSIVE) {
pb.setVisibility(View.VISIBLE);
pb.setMax(award.requirement);
pb.setProgress(award.current_amount);
} else {
pb.setVisibility(View.GONE);
}
((TextView) v.findViewById(R.id.tvwShortMessage)).
setText(MessageBuilder.buildMessage(AwardsActivity.this, award.award_text,
Integer.valueOf(award.requirement).toString()));
return v;
}
I actually want the opposite - the bar without the spinner.
Is this problem due to the special way this is being displayed or am i doing something wrong with the progress bar?
Resulting screen:
One way of doing this would be to create a class which extends a subclass of BaseAdapter, for example ArrayAdapter, and in your getView() method either inflate one of two different views (with an image and text in your case or text and a progressbar), or inflate one view and then set the visibility of the different view elements using setVisibility() to create the desired final layout.
For example:
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater)YourActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.your_layout, parent, false);
}
ListObject o = items.get(position);
if(o.someCondition) {
v.findViewById(R.id.view_element).setVisibility(View.GONE);
} else {
v.findViewById(R.id.some_other_view).setVisibility(View.GONE);
}
}

Categories

Resources