SwipeListView stays in "swiped state" after deleting a row - android

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()).

Related

Android custom ArrayAdapter rendering Objects in list, not values

I'm trying to bind a custom layout (a LinearLayout containing two TextViews) to a Spinner. I subclassed ArrayAdapter (mostly) correctly. Selecting an item in the Spinner calls getView() correctly, setting the LinearLayout's TextViews' values correctly. The problem is the initial display of the items in the Spinner (when clicking on the Spinner) just shows Objects; not the TextViews they should be displaying. Only AFTER clicking on one of the Objects does the Spinner correctly set the TextViews using my custom adapter's getView() method. Here's the custom adapter class:
public class BusRouteAdapter extends ArrayAdapter<BusRoute> {
...
public BusRouteAdapter(Context context, int resource, int textViewId, ArrayList<BusRoute> routes) {
super(context, resource, textViewId, routes);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
BusRoute route = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.bus_route, parent, false);
}
TextView tvBusRoute = (TextView) convertView.findViewById(R.id.tvBusRoute);
TextView tvBusRouteNumber = (TextView) convertView.findViewById(R.id.tvBusRouteNumber);
tvBusRoute.setText(route.routeName);
tvBusRoute.setTag(route.route);
tvBusRouteNumber.setText(route.route);
if (!route.routeColor.equals("")) {
tvBusRouteNumber.setBackgroundColor(Color.parseColor(route.routeColor));
}
return convertView;
}
}
Here's the layout that is to be used for each Spinner list item (bus_route.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="1">
<TextView
android:id="#+id/tvBusRouteNumber"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.25"
android:padding="8dp" />
<TextView
android:id="#+id/tvBusRoute"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.75"
android:padding="8dp" />
</LinearLayout>
And in my Activity, I'm setting the adapter to a properly-populated list of BusRoute objects:
busRouteAdapter = new BusRouteAdapter(getBaseContext(), R.layout.bus_route, R.id.tvBusRoute, arrayOfBusRoutes);
routesSpinner.setAdapter(busRouteAdapter);
It does seem strange that I need to pass the Layout id (R.layout.bus_route) AND one of the TextViews contained in that Layout (R.id.tvBusRoute).
Here's what is rendered when clicking on the Spinner:
But if I click one of the Objects, getView() is called, and the selected Layout and TextViews are rendered properly (apparently I selected "GMU - Pentagon"):
What am I missing to get the Spinner's popup list to display ALL my bus route items rendered correctly?
I think you need to override getDropDownView to deal with spinners
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
BusRoute route = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.bus_route, parent, false);
}
TextView tvBusRoute = (TextView) convertView.findViewById(R.id.tvBusRoute);
TextView tvBusRouteNumber = (TextView) convertView.findViewById(R.id.tvBusRouteNumber);
tvBusRoute.setText(route.routeName);
tvBusRoute.setTag(route.route);
tvBusRouteNumber.setText(route.route);
if (!route.routeColor.equals("")) {
tvBusRouteNumber.setBackgroundColor(Color.parseColor(route.routeColor));
}
return convertView;
}

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);

Invisible TextView's start to be visible when scrolling ListView

I've got huge problem that I don't understand. I have got custom ListView with my own adapter. Each row in ListView has two TextViews - one is a title and the second one is invisible at start. It's going to be visible when there is something new in this item.
In my adapter I set title for every row and set second TextView to be visible when it should be. When I run my app it's fine, but when i scroll down and up the list almost every row is changing invisible TextView to be visible!
I don't know why, but I spaculate that this is something with convertView. Can anybody tell me what's going on?
My adapter:
public class MyListAdapter extends BaseAdapter {
#SuppressWarnings("unused")
private Activity activity;
private ArrayList<String> titles;
private LayoutInflater inflater;
public MyListAdapter (Activity activity, ArrayList<String> titles) {
this.activity = activity;
this.titles = titles;
inflater = (LayoutInflater)activity.getLayoutInflater();
}
public int getCount() {
return titles.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
if (convertView == null) {
vi = inflater.inflate(R.layout.wpis_list_row, null);
}
TextView title = (TextView)vi.findViewById(R.id.movie_title);
title.setText(titles.get(position));
String name = titles.get(position);
if (name.equals("Name 1") || name.equals("Name 2")
|| name.equals("Name 3")) {
TextView news = (TextView)vi.findViewById(R.id.new_sounds);
news.setVisible(0);
}
return vi;
}
}
and my layout for single row:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/LinearLayout1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#drawable/list_selector"
android:orientation="horizontal"
android:padding="5dip" >
<TextView
android:id="#+id/movie_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="9"
android:layout_marginLeft="#dimen/list_margin"
android:text="#string/entry"
android:textColor="#FFFFFF"
android:textSize="22sp" />
<TextView
android:id="#+id/new_sounds"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:textColor="#8C1717"
android:textSize="13sp"
android:text="#string/news"
android:visibility="invisible" />
</LinearLayout>
Sometimes you are setting the visibility of the text view as visible, based on some if condition . Make sure to change the visibility to gone in the else part.I hope this work . Also you are assigning view view= convertview and then doing if( convertview = null) . Assign the view to convert view in the else part of this check.
To make a View invisible in its layout, use android:visibility attribute set to gone or invisible depending on what you want to do.
Programmatically, to change visibility, call the View.setVisibility(VISIBILITY_YOU_WANT) method. There are some constants in View class you can use: View.GONE , View.VISIBLE , View.INVISIBLE.
BTW, read this post to better understand the ListView recycling.

Android GridView Item Selectors change position and duplicate

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.

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