[android] Swipe ListView item From right to left show delete button

I have a custom ListView showing the list of words selecting from database. When I swipe this listview item i want to show Delete button like image below. And when I press that button it is deleted from Database and refresh the listview. mSwipe Listview item

I already look in this sample code here. But it still does not work.

This question is related to android android-listview swipe-gesture

The answer is


i've searched google a lot and find the best suited project is the swipmenulistview https://github.com/baoyongzhang/SwipeMenuListView on github.


see there link was very nice and simple. its working fine... u don't want any library its working fine. click here

OnTouchListener gestureListener = new View.OnTouchListener() {
    private int padding = 0;
    private int initialx = 0;
    private int currentx = 0;
    private  ViewHolder viewHolder;

    public boolean onTouch(View v, MotionEvent event) {
        if ( event.getAction() == MotionEvent.ACTION_DOWN) {
            padding = 0;
            initialx = (int) event.getX();
            currentx = (int) event.getX();
            viewHolder = ((ViewHolder) v.getTag());
        }
        if ( event.getAction() == MotionEvent.ACTION_MOVE) {
            currentx = (int) event.getX();
            padding = currentx - initialx;
        }       
        if ( event.getAction() == MotionEvent.ACTION_UP || 
                     event.getAction() == MotionEvent.ACTION_CANCEL) {
            padding = 0;
            initialx = 0;
            currentx = 0;
        }
        if(viewHolder != null) {
            if(padding == 0) {
                v.setBackgroundColor(0xFF000000 );  
                if(viewHolder.running)
                    v.setBackgroundColor(0xFF058805);
            }
            if(padding > 75) {
                viewHolder.running = true;
                v.setBackgroundColor(0xFF00FF00 );  
                viewHolder.icon.setImageResource(R.drawable.clock_running);
            }
            if(padding < -75) {
                viewHolder.running = false;
                v.setBackgroundColor(0xFFFF0000 );  
            }

            v.setPadding(padding, 0,0, 0);
        }

        return true;
    }
};

you can use this code

holder.img_close.setOnClickListener(new View.OnClickListener() {
      @Override
        public void onClick(View view) {
            holder.swipeRevealLayout.close(true);
            list.remove(position);
            notifyDataSetChanged();
      }});

I had created a demo on my github which includes on swiping from right to left a delete button will appear and you can then delete your item from the ListView and update your ListView.


It's a lose of time to implement from scratch this functionality. I implemented the library recommended by SalutonMondo and I am very satisfied. It is very simple to use and very quick. I improved the original library and I added a new click listener for item click. Also I added font awesome library (http://fortawesome.github.io/Font-Awesome/) and now you can simply add a new item title and specify the icon name from font awesome.

Here is the github link


An app is available that demonstrates a listview that combines both swiping-to-delete and dragging to reorder items. The code is based on Chet Haase's code for swiping-to-delete and Daniel Olshansky's code for dragging-to-reorder.

Chet's code deletes an item immediately. I improved on this by making it function more like Gmail where swiping reveals a bottom view that indicates that the item is deleted but provides an Undo button where the user has the possibility to undo the deletion. Chet's code also has a bug in it. If you have less items in the listview than the height of the listview is and you delete the last item, the last item appears to not be deleted. This was fixed in my code.

Daniel's code requires pressing long on an item. Many users find this unintuitive as it tends to be a hidden function. Instead, I modified the code to allow for a "Move" button. You simply press on the button and drag the item. This is more in line with the way the Google News app works when you reorder news topics.

The source code along with a demo app is available at: https://github.com/JohannBlake/ListViewOrderAndSwipe

Chet and Daniel are both from Google.

Chet's video on deleting items can be viewed at: https://www.youtube.com/watch?v=YCHNAi9kJI4

Daniel's video on reordering items can be viewed at: https://www.youtube.com/watch?v=_BZIvjMgH-Q

A considerable amount of work went into gluing all this together to provide a seemless UI experience, so I'd appreciate a Like or Up Vote. Please also star the project in Github.


Define a ViewPager in your layout .xml:

<android.support.v4.view.ViewPager
    android:id="@+id/example_pager"
    android:layout_width="fill_parent"
    android:layout_height="@dimen/abc_action_bar_default_height" />

And then, in your activity / fragment, set a custom pager adapter:

In an activity:

protected void onCreate(Bundle savedInstanceState) {
    PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager());
    ViewPager pager = (ViewPager) findViewById(R.id.example_pager);

    pager.setAdapter(adapter);
    // pager.setOnPageChangeListener(this); // You can set a page listener here
    pager.setCurrentItem(0);
}

In a fragment:

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_layout, container, false);

    if (view != null) {
        PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager());
        ViewPager pager = (ViewPager) view.findViewById(R.id.example_pager);

        pager.setAdapter(adapter);
        // pager.setOnPageChangeListener(this); // You can set a page listener here
        pager.setCurrentItem(0);
    }

    return view;
}

Create our custom pager class:

// setup your PagerAdapter which extends FragmentPagerAdapter
class PagerAdapter extends FragmentPagerAdapter {
    public static final int NUM_PAGES = 2;
    private CustomFragment[] mFragments = new CustomFragment[NUM_PAGES];
    public PagerAdapter(FragmentManager fragmentManager) {
        super(fragmentManager);
    }
    @ Override
    public int getCount() {
        return NUM_PAGES;
    }
    @ Override
    public Fragment getItem(int position) {
        if (mFragments[position] == null) {
               // this calls the newInstance from when you setup the ListFragment
            mFragments[position] = new CustomFragment();
        }
        return mFragments[position];
    }
}

EDIT: between other options there's a nice library that could solve your issue: https://github.com/daimajia/AndroidSwipeLayout


I used to have the same problem finding a good library to do that. Eventually, I created a library which can do that: SwipeRevealLayout

In gradle file:

dependencies {
    compile 'com.chauthai.swipereveallayout:swipe-reveal-layout:1.4.0'
}

In your xml file:

<com.chauthai.swipereveallayout.SwipeRevealLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:mode="same_level"
    app:dragEdge="left">

    <!-- Your secondary layout here -->
    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="match_parent" />

    <!-- Your main layout here -->
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</com.chauthai.swipereveallayout.SwipeRevealLayout>

Then in your adapter file:

public class Adapter extends RecyclerView.Adapter {
  // This object helps you save/restore the open/close state of each view
  private final ViewBinderHelper viewBinderHelper = new ViewBinderHelper();

  @Override
  public void onBindViewHolder(ViewHolder holder, int position) {
    // get your data object first.
    YourDataObject dataObject = mDataSet.get(position); 

    // Save/restore the open/close state.
    // You need to provide a String id which uniquely defines the data object.
    viewBinderHelper.bind(holder.swipeRevealLayout, dataObject.getId()); 

    // do your regular binding stuff here
  }
}

I have gone through tons of third party libraries to try to achieve this. But none of them exhibits smoothness and usability experience which i wanted. Then i decided to write it myself. And the result was , well, i loved it. I will share the code here. Maybe i will write it as a library which can be embedded in any recycler view in future. But for now here is the code.

Note: i have uses recycler view and ViewHolder. Some values are hardcoded so change them according to your requirement.

  • row_layout.xml

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:orientation="horizontal">
        <Button
            android:id="@+id/slide_button_2"
            android:text="Button2"
            android:layout_width="80dp"
            android:layout_height="80dp" />
        <Button
            android:id="@+id/slide_button_1"
            android:text="Button1"
            android:layout_width="80dp"
            android:layout_height="80dp" />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/chat_row_cell"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:orientation="horizontal"
        android:background="@color/white">
        <ImageView
            android:id="@+id/chat_image"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:layout_marginRight="10dp"
            android:layout_gravity="center"/>
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">
                <TextView
                    android:id="@+id/chat_title"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textColor="@color/md_grey_800"
                    android:textSize="18sp"/>
                <TextView
                    android:id="@+id/chat_subtitle"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textColor="@color/md_grey_600"/>
            </LinearLayout>
        </LinearLayout>
    </LinearLayout>
    

  • ChatAdaptor.java

    public class ChatAdaptor extends RecyclerView.Adapter {

    List<MXGroupChatSession> sessions;
    Context context;
    ChatAdaptorInterface listener;
    
    public interface ChatAdaptorInterface{
        void cellClicked(MXGroupChatSession session);
        void utilityButton1Clicked(MXGroupChatSession session);
        void utilityButton2Clicked(MXGroupChatSession session);
    }
    
    public ChatAdaptor(List<MXGroupChatSession> sessions, ChatAdaptorInterface listener, Context context){
        this.sessions=sessions;
        this.context=context;
        this.listener=listener;
    }
    
    @Override
    public ChatViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view=(View)LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_row,null);
        ChatViewHolder chatViewHolder=new ChatViewHolder(view);
        return chatViewHolder;
    }
    
    @Override
    public void onBindViewHolder(ChatViewHolder holder, final int position) {
        MXGroupChatSession session=this.sessions.get(position);
        holder.selectedSession=session;
        holder.titleView.setText(session.getTopic());
        holder.subtitleView.setText(session.getLastFeedContent());
        Picasso.with(context).load(new File(session.getCoverImagePath())).transform(new CircleTransformPicasso()).into(holder.imageView);
    }
    
    @Override
    public int getItemCount() {
        return sessions.size();
    }
    
    public class ChatViewHolder extends RecyclerView.ViewHolder{
        ImageView imageView;
        TextView titleView;
        TextView subtitleView;
        ViewGroup cell;
        ViewGroup cellContainer;
        Button button1;
        Button button2;
    
        MXGroupChatSession selectedSession;
        private GestureDetectorCompat gestureDetector;
    
        float totalx;
        float buttonTotalWidth;
    
        Boolean open=false;
        Boolean isScrolling=false;
    
    
        public ChatViewHolder(View itemView) {
            super(itemView);
            cell=(ViewGroup) itemView.findViewById(R.id.chat_row_cell);
            cellContainer=(ViewGroup) itemView.findViewById(R.id.chat_row_container);
            button1=(Button) itemView.findViewById(R.id.slide_button_1);
            button2=(Button) itemView.findViewById(R.id.slide_button_2);
    
            button1.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    listener.utilityButton1Clicked(selectedSession);
                }
            });
    
            button2.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    listener.utilityButton2Clicked(selectedSession);
                }
            });
    
            ViewTreeObserver vto = cellContainer.getViewTreeObserver();
            vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    buttonTotalWidth = button1.getWidth()+button2.getWidth();
                }
            });
    
            this.titleView=(TextView)itemView.findViewById(R.id.chat_title);
            subtitleView=(TextView)itemView.findViewById(R.id.chat_subtitle);
            imageView=(ImageView)itemView.findViewById(R.id.chat_image);
            gestureDetector=new GestureDetectorCompat(context,new ChatRowGesture());
    
            cell.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if(gestureDetector.onTouchEvent(event)){
                        return true;
                    }
    
                    if(event.getAction() == MotionEvent.ACTION_UP) {
                        if(isScrolling ) {
                            isScrolling  = false;
                            handleScrollFinished();
                        };
                    }
                    else if(event.getAction() == MotionEvent.ACTION_CANCEL){
                        if(isScrolling ) {
                            isScrolling  = false;
                            handleScrollFinished();
                        };
                    }
                    return false;
                }
            });
        }
    
    
        public class ChatRowGesture extends GestureDetector.SimpleOnGestureListener {
    
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                if (!open){
                    listener.cellClicked(selectedSession);
                }
                return true;
            }
    
            @Override
            public boolean onDown(MotionEvent e) {
                return true;
            }
    
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                    isScrolling=true;
                    totalx=totalx+distanceX;
                    freescroll(totalx);
    
                return true;
            }
        }
    
        void handleScrollFinished(){
            if (open){
                if (totalx>2*buttonTotalWidth/3){
                    slideLeft();
                    totalx=buttonTotalWidth;
                }else{
                    slideRight();
                    totalx=0;
                }
            }else{
                if (totalx>buttonTotalWidth/3){
                    slideLeft();
                    totalx=buttonTotalWidth;
                }else{
                    slideRight();
                    totalx=0;
                }
            }
    
        }
    
        void slideRight(){
            TransitionManager.beginDelayedTransition(cellContainer);
            ViewGroup.MarginLayoutParams params;
            params=(ViewGroup.MarginLayoutParams) cell.getLayoutParams();
            params.setMargins(0,0,0,0);
            cell.setLayoutParams(params);
            open=false;
        }
    
        void slideLeft(){
            TransitionManager.beginDelayedTransition(cellContainer);
            ViewGroup.MarginLayoutParams params;
            params=(ViewGroup.MarginLayoutParams) cell.getLayoutParams();
            params.setMargins(((int)buttonTotalWidth*-1),0,(int)buttonTotalWidth,0);
            cell.setLayoutParams(params);
            open=true;
        }
        void freescroll(float x){
            if (x<buttonTotalWidth && x>0){
                int xint=(int)x;
                ViewGroup.MarginLayoutParams params;
                params=(ViewGroup.MarginLayoutParams) cell.getLayoutParams();
                params.setMargins(params.leftMargin,0,xint,0);
                cell.setLayoutParams(params);
            }
        }
    }
    

Hope this helps someone!!


I just got his working using the ViewSwitcher in a ListItem.

list_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >

<ViewSwitcher
    android:id="@+id/list_switcher"
    android:layout_width="match_parent"
    android:layout_height="fill_parent"
    android:inAnimation="@android:anim/slide_in_left"
    android:outAnimation="@android:anim/slide_out_right"
    android:measureAllChildren="false" >
    <TextView
        android:id="@+id/tv_item_name"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_gravity="center_vertical"
        android:maxHeight="50dp"
        android:paddingLeft="10dp" />

    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:clickable="false"
        android:gravity="center"
        >

    <Button
        android:id="@+id/b_edit_in_list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Edit"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"

         />
    <Button
        android:id="@+id/b_delete_in_list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Delete"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:background="@android:color/holo_red_dark"
        />
    </LinearLayout>
</ViewSwitcher>

In the ListAdapter: Implement OnclickListeners for the Edit and Delete button in the getView() method. The catch here is to get the position of the ListItem clicked inside the onClick methods. setTag() and getTag() methods are used for this.

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    // TODO Auto-generated method stub
    final ViewHolder viewHolder;
    if (convertView == null) {
        viewHolder = new ViewHolder();
        convertView = mInflater.inflate(R.layout.list_item, null);
        viewHolder.viewSwitcher=(ViewSwitcher)convertView.findViewById(R.id.list_switcher);
        viewHolder.itemName = (TextView) convertView
                .findViewById(R.id.tv_item_name);
        viewHolder.deleteitem=(Button)convertView.findViewById(R.id.b_delete_in_list);
        viewHolder.deleteItem.setTag(position);
        viewHolder.editItem=(Button)convertView.findViewById(R.id.b_edit_in_list);
        viewHolder.editItem.setTag(position);
        viewHolder.deleteItem.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                fragment.deleteItemList((Integer)v.getTag());
            }
        });

        viewHolder.editItem.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                fragment.editItemList(position);
            }
        });
        convertView.setTag(viewHolder);
} else {
        viewHolder = (ViewHolder) convertView.getTag();
    }

    viewHolder.itemName.setText(itemlist[position]);
return convertView;  
}

In the Fragment, Add a Gesture Listener to detect the Fling Gesture:

public class MyGestureListener extends SimpleOnGestureListener {
    private ListView list;

    public MyGestureListener(ListView list) {
        this.list = list;
    }

    // CONDITIONS ARE TYPICALLY VELOCITY OR DISTANCE

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
            float velocityY) {
        // if (INSERT_CONDITIONS_HERE)

        ltor=(e2.getX()-e1.getX()>DELTA_X);
        if (showDeleteButton(e1))
        {
            return true;
        }
        return super.onFling(e1, e2, velocityX, velocityY);
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2,
            float distanceX, float distanceY) {
        // TODO Auto-generated method stub
        return super.onScroll(e1, e2, distanceX, distanceY);
    }

    private boolean showDeleteButton(MotionEvent e1) {
        int pos = list.pointToPosition((int) e1.getX(), (int) e1.getY());
        return showDeleteButton(pos);
    }

    private boolean showDeleteButton(int pos) {

        View child = list.getChildAt(pos);
        if (child != null) {
            Button delete = (Button) child
                    .findViewById(R.id.b_edit_in_list);
            ViewSwitcher viewSwitcher = (ViewSwitcher) child
                    .findViewById(R.id.host_list_switcher);
            TextView hostName = (TextView) child
                    .findViewById(R.id.tv_host_name);
            if (delete != null) {
                    viewSwitcher.setInAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.slide_in_left));
                    viewSwitcher.setOutAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.slide_out_right));
                }

                viewSwitcher.showNext();
                // frameLayout.setVisibility(View.VISIBLE);
            }
            return true;
        }
        return false;
    }
}

In the onCreateView method of the Fragment,

GestureDetector gestureDetector = new GestureDetector(getActivity(),
            new MyGestureListener(hostList));
    hostList.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // TODO Auto-generated method stub
            if (gestureDetector.onTouchEvent(event)) {
                return true;
            } else {
                return false;
            }

        }
    });

This worked for me. Should refine it more.