SlidingPaneLayout implementing swipe to remove items in list and undo action.
In my previous post I described how to work with the new SlidingPaneLayout.
Some readers asked me how to remove items from list with swipe gesture like in Google Hangouts or Gmail.
It is not very simple.... but I realized something like this.
As always it is just an example,do not take this code too seriously.
First of all, we have to integrate list with a SwipeListener.
There is a beatiful example in dashclock by Roman Nurik...so why not, we can use it.
You can find it here:SwipeDismissListViewTouchListener.
Pay attention: In source you can find:
// THIS IS A BETA! I DON'T RECOMMEND USING IT IN PRODUCTION CODE JUST YET....
We'll run the risk ... Dashclock is in Google Play, and I think that it will be maintained for a long time.
Now we can integrate our MyListFragment with this Listener.
It is very simple.
The method canDismiss is very useful with sectioned listviews, if the item cannot be dismissed.
It is enough to have a list with a simple swipe gesture implemented.
Now the listener animates items out when we have a space greater than a half or when we have a fast motion.
We can change this behavior, by changing these lines in SwipeDismissListViewTouchListener:
Now we'll try to insert undo action.
Also in this case, we can find a awesome example by Roman Nurik...(thanks again for your code!).
You can find source here, and you can read about it in a Google+ post.
We'll copy UndoBarController's code, and we'll integrate our list.
First of all, we modify our fragment layout to insert a hidden undobar. Here you can find new list_fragment.xml.
You can get code from GitHub:
Some readers asked me how to remove items from list with swipe gesture like in Google Hangouts or Gmail.
It is not very simple.... but I realized something like this.
As always it is just an example,do not take this code too seriously.
First of all, we have to integrate list with a SwipeListener.
There is a beatiful example in dashclock by Roman Nurik...so why not, we can use it.
You can find it here:SwipeDismissListViewTouchListener.
Pay attention: In source you can find:
// THIS IS A BETA! I DON'T RECOMMEND USING IT IN PRODUCTION CODE JUST YET....
We'll run the risk ... Dashclock is in Google Play, and I think that it will be maintained for a long time.
Now we can integrate our MyListFragment with this Listener.
It is very simple.
@Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // We use a arrayList to prevent UnsupportedOperationException // The ArrayAdapter, on being initialized by an array, converts the // array into a AbstractList (List) which cannot be modified. ArrayListSwipeDismissListViewTouchListener does everything we need, manages swipe gestures, animations and we have to implement only listener's callback to remove items from our adapter.itemslist = new ArrayList (); itemslist.addAll(Arrays.asList(items)); mAdapter = new ArrayAdapter (getActivity(), android.R.layout.simple_list_item_1, itemslist); setListAdapter(mAdapter); // Create a ListView-specific touch listener. mListView = getListView(); if (mListView != null) { mOnTouchListener = new SwipeDismissListViewTouchListener(mListView,mCallback); mListView.setOnTouchListener(mOnTouchListener); // Setting this scroll listener is required to ensure that during // ListView scrolling, // we don't look for swipes. mListView.setOnScrollListener(mOnTouchListener.makeScrollListener()); } }
public class MyListFragment extends ListFragment { /** * SwipeDismiss callback * * Remove items, and show undobar */ SwipeDismissListViewTouchListener.DismissCallbacks mCallback = new SwipeDismissListViewTouchListener.DismissCallbacks() { @Override public void onDismiss(ListView listView, int[] reverseSortedPositions) { for (int position : reverseSortedPositions) { String itemString=mAdapter.getItem(position); mAdapter.remove(itemString); } mAdapter.notifyDataSetChanged(); } @Override public boolean canDismiss(int position) { return position <= mAdapter.getCount() - 1; } }; }The method onDismiss removes items form Adapter and notifies changes.
The method canDismiss is very useful with sectioned listviews, if the item cannot be dismissed.
It is enough to have a list with a simple swipe gesture implemented.
Now the listener animates items out when we have a space greater than a half or when we have a fast motion.
We can change this behavior, by changing these lines in SwipeDismissListViewTouchListener:
if (Math.abs(deltaX) > mViewWidth / 2) { dismiss = true; dismissRight = deltaX > 0; } else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity && absVelocityY < absVelocityX) { // dismiss only if flinging in the same direction as dragging dismiss = (velocityX < 0) == (deltaX < 0); dismissRight = mVelocityTracker.getXVelocity() > 0; }
Now we'll try to insert undo action.
Also in this case, we can find a awesome example by Roman Nurik...(thanks again for your code!).
You can find source here, and you can read about it in a Google+ post.
We'll copy UndoBarController's code, and we'll integrate our list.
First of all, we modify our fragment layout to insert a hidden undobar. Here you can find new list_fragment.xml.
Then we modify our MyListFragment to use this layout:
Now we instantiate an undoBarController:@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { setHasOptionsMenu(true); return inflater.inflate(R.layout.list_fragment, container, false); //return super.onCreateView(inflater, container, savedInstanceState); }
@Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); ..... if (mListView != null) { mOnTouchListener = new SwipeDismissListViewTouchListener(mListView,mCallback); mListView.setOnTouchListener(mOnTouchListener); ..... //UndoController if (mUndoBarController==null) mUndoBarController = new UndoBarController( getActivity().findViewById(R.id.undobar), this); } }Now we can modify DismissCallback to show undobar.
public class MyListFragment extends ListFragment{ /** * SwipeDismiss callback * * Remove items, and show undobar */ SwipeDismissListViewTouchListener.DismissCallbacks mCallback = new SwipeDismissListViewTouchListener.DismissCallbacks() { @Override public void onDismiss(ListView listView, int[] reverseSortedPositions) { String[] itemStrings=new String[reverseSortedPositions.length]; int[] itemPositions=new int[reverseSortedPositions.length]; int i=0; for (int position : reverseSortedPositions) { String itemString=mAdapter.getItem(position); mAdapter.remove(itemString); itemStrings[i]=itemString; itemPositions[i]=position; i++; } mAdapter.notifyDataSetChanged(); //Show UndoBar UndoItem itemUndo=new UndoItem(itemStrings,itemPositions); //Undobar message Resources res = getResources(); String messageUndoBar = res.getQuantityString(R.plurals.items, reverseSortedPositions.length,reverseSortedPositions.length); mUndoBarController.showUndoBar( false, messageUndoBar, itemUndo); } @Override public boolean canDismiss(int position) { return position <= mAdapter.getCount() - 1; } }; }UndoItem is a simple Parcelable object which stores data by items removed.
public class UndoItem implements Parcelable { public String[] itemString; public int[] itemPosition; public UndoItem(String[] itemString, int[] itemPosition) { this.itemString = itemString; this.itemPosition = itemPosition; } protected UndoItem(Parcel in) {} public int describeContents() { return 0; } public void writeToParcel(Parcel dest, int flags) {} public static final Parcelable.CreatorAs a final step, we have to implement undobar callback.CREATOR = new Parcelable.Creator () { public UndoItem createFromParcel(Parcel in) { return new UndoItem(in); } public UndoItem[] newArray(int size) { return new UndoItem[size]; } }; }
public class MyListFragment extends ListFragment implements UndoBarController.UndoListener { /** * Undo remove Action */ @Override public void onUndo(Parcelable token) { //Restore items in lists (use reverseSortedOrder) if (token!=null){ //Retrieve data from token UndoItem item=(UndoItem)token; String[] itemStrings=item.itemString; int[] itemPositions=item.itemPosition; if (itemStrings!=null && itemPositions!=null){ int end=itemStrings.length; for(int i=end-1;i>=0;i--){ String itemString=itemStrings[i]; int itemPosition=itemPositions[i]; mAdapter.insert(itemString,itemPosition); mAdapter.notifyDataSetChanged(); } } } } }That's all... it works...(and certainly can be improved).
You can get code from GitHub:
Comments
Post a Comment