April 8, 2014

How to Implement ListView with swipe gestures - SwipeListView

ListViews are one of the most used widgets in Android because of their customizability. Using swipe gestures on ListViews can be very useful to provide an easy to use, stunning UI and awesome UX.

We can use swipe gestures within each ListView item to allow the user to perform a particular action or change the UI of the ListView row item.

This tutorial will help you to implement a custom ListView which initially display some details in TextViews and; when the user swipes from Right to Left on a list item; few ImageButtons appear from the right direction and the details slide out to the left.


I will try do this without the usage of any external libraries or APIs. Using the stock widgets(Views) of android and some simple animation using XMLs.


How the ListView will look and behave?

How the ListView will behave

How the project is organized?


  • /res/anim folder which contains the 4 animation files we will use for animations on swipes. The files inside this folder are self-explanatory.

  • list_row_item.xml is the layout for our custom ListView's row item.

  • MyGestureListener.java is a custom SimpleOnGestureListener which will be used to perform defined gesture actions on each item of the ListView.

  • MyListAdapter.java is the custom BaseAdapter which will be used to display ListView's content.

  •  MainActivity.java ( activity_main.xml ) is the main layout of our project. It is just a normal Activity which contain a ListView.


Create a new project and create a new xml file named list_row_item.xml in the /res/layout folder.

 list_row_item.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="?android:attr/listPreferredItemHeightLarge" >
    
 <ImageView
     android:id="@+id/userimage"
     android:layout_height="80dp"
     android:layout_width="60dp"
     android:layout_alignParentLeft="true"
     android:layout_centerVertical="true"
     android:layout_margin="10dp"
     android:src="@drawable/ic_launcher"
     android:contentDescription="@string/app_name" />
 
 <LinearLayout
     android:id="@+id/layout_front"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
     android:layout_toRightOf="@id/userimage"
     android:layout_centerVertical="true"
     android:gravity="center_vertical"
     android:orientation="vertical" >
     
     <TextView
         android:id="@+id/name"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:text="User Name"
         android:textAppearance="?android:attr/textAppearanceMedium" />
     <TextView
         android:id="@+id/detail1"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:text="user detail 1"
         android:textAppearance="?android:attr/textAppearanceSmall" />
     <TextView
         android:id="@+id/detail2"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:text="user detail 2"
         android:textAppearance="?android:attr/textAppearanceSmall" />
     
 </LinearLayout>
 
 <RelativeLayout
     android:id="@+id/layout_back"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
     android:layout_toRightOf="@id/userimage"
     android:paddingLeft="30dp"
     android:paddingRight="30dp"
     android:layout_centerVertical="true"
     android:visibility="gone"
     android:gravity="center_vertical" >
     
     <ImageView
         android:id="@+id/btn1"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:padding="5dp"
         android:layout_alignParentLeft="true"
         android:layout_centerVertical="true"
         android:src="@drawable/phone_icon"
         android:contentDescription="@string/app_name" />
     <ImageView
         android:id="@+id/btn2"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:padding="5dp"
         android:layout_centerInParent="true"
         android:layout_centerVertical="true"
         android:src="@drawable/message_icon"
         android:contentDescription="@string/app_name" />
     <ImageView
         android:id="@+id/btn3"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:padding="5dp"
         android:layout_alignParentRight="true"
         android:layout_centerVertical="true"
         android:src="@drawable/email_icon"
         android:contentDescription="@string/app_name" />
     
 </RelativeLayout>
    
</RelativeLayout>

Now create a folder named anim in the /res folder of your project. As mentioned earlier this folder will be used to keep the animation files. After that you need to create the following four XML files in the /res/anim folder:

in_from_left.xml

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="-100%p"
    android:toXDelta="0"
    android:duration="800" />

in_from_right.xml

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="100%p"
    android:toXDelta="0"
    android:duration="800" />

out_to_left.xml

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="0"
    android:toXDelta="-100%p"
    android:duration="800" />

 out_to_right.xml

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="800"
    android:fromXDelta="0"
    android:interpolator="@android:anim/linear_interpolator"
    android:toXDelta="100%p" />

MyListAdapter.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.example.myswipelistview;

import android.content.Context;
import android.support.v4.view.GestureDetectorCompat;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class MyListAdapter extends BaseAdapter {

    private Context ctx;
    private String[] names;

    public MyListAdapter(Context ctx, String[] data) {
        this.ctx = ctx;
        this.names = data;
    }

    static class ViewHolder {
        RelativeLayout container;
        TextView userName;
        GestureDetectorCompat mDetector;
    }

    @Override
    public int getCount() {
        return names.length;
    }

    @Override
    public Object getItem(int arg0) {
        return names[arg0];
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = LayoutInflater.from(ctx).inflate(
                    R.layout.list_row_item, null);
            final ViewHolder holder = new ViewHolder();
            holder.container = (RelativeLayout) convertView
                    .findViewById(R.id.container);
            holder.userName = (TextView) convertView.findViewById(R.id.name);
            holder.mDetector = new GestureDetectorCompat(ctx,
                    new MyGestureListener(ctx, convertView));
            convertView.setTag(holder);

        }
        final ViewHolder holder = (ViewHolder) convertView.getTag();
        holder.userName.setText(names[position]);
        holder.container.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                holder.mDetector.onTouchEvent(event);
                return true;
            }
        });

        return convertView;
    }

}

MyGestureListener.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.example.myswipelistview;

import android.content.Context;
import android.util.Log;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

public class MyGestureListener extends SimpleOnGestureListener {

   private static final int MIN_DISTANCE = 50;
   private static final String TAG = "MyGestureListener";
   private RelativeLayout backLayout;
   private LinearLayout frontLayout;
   private Animation inFromRight,outToRight,outToLeft,inFromLeft;
   

   public MyGestureListener(Context ctx,View convertView) {
      backLayout = (RelativeLayout) convertView.findViewById(R.id.layout_back);
      frontLayout = (LinearLayout) convertView.findViewById(R.id.layout_front);
      inFromRight = AnimationUtils.loadAnimation(ctx, R.anim.in_from_right);
      outToRight = AnimationUtils.loadAnimation(ctx, R.anim.out_to_right);
      outToLeft = AnimationUtils.loadAnimation(ctx, R.anim.out_to_left);
      inFromLeft = AnimationUtils.loadAnimation(ctx, R.anim.in_from_left);
   }

   @Override
   public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
         float velocityY) {
      float diffX = e2.getX() - e1.getX();
      float diffY = e2.getY() - e1.getY();
      if (Math.abs(diffX) > Math.abs(diffY)) {
         if (Math.abs(diffX) > MIN_DISTANCE) {
            if(diffX<0){
               Log.v(TAG, "Swipe Right to Left");
               if(backLayout.getVisibility()==View.GONE){
                  frontLayout.startAnimation(outToLeft);
                  backLayout.setVisibility(View.VISIBLE);
                  backLayout.startAnimation(inFromRight);
                  frontLayout.setVisibility(View.GONE);
               }
            }else{
               Log.v(TAG, "Swipe Left to Right");
               if(backLayout.getVisibility()!=View.GONE){
                  backLayout.startAnimation(outToRight);
                  backLayout.setVisibility(View.GONE);
                  frontLayout.setVisibility(View.VISIBLE);
                  frontLayout.startAnimation(inFromLeft);
               }
            }
         }
      }

      return true;
   }
   
}

MainActivity.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.example.myswipelistview;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;

public class MainActivity extends Activity {

   ListView listView;
   String[] names={"User 1","User 2","User 3","User 4","User 5","User 6"};
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      
      listView = (ListView) findViewById(R.id.listView1);
      MyListAdapter adapter = new MyListAdapter(this,names);
      listView.setAdapter(adapter);
   }

}

activity_main.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <ListView
        android:id="@+id/listView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:divider="#cccccc"
        android:dividerHeight="2dp" >
    </ListView>

</RelativeLayout>

Feel free to post your comments, queries and suggestions. Cheers... ;)

7 comments:

  1. how I can run function when click on message icon in row two for example?

    thanks in advance

    ReplyDelete
    Replies
    1. You can implement OnClickListener on the desired button

      Delete
  2. gud mng sir i am new to android i implemented same swipe action on listview .it works perfectly for 3 or below 3 items.for more than 3 (ex 6 listview items) i got a position problem.
    if 1st item swiped action performed on 1 st item as well as 5 th item ....
    plz give solution how to overcome that problem. thanking u sir

    ReplyDelete
    Replies
    1. You can try the above example without using the ViewHolder Pattern

      Delete
    2. Thanks, I have tried using ViewHolder still the same. touch event does not work

      Delete
  3. After implementing this, my onItemClick is not working...
    Any solution please...

    ReplyDelete
  4. I'm also facing the same problem, without using ViewHolder means to remove complete ViewHolder Menthod and its usage. I've tried commenting ViewHolder everywhere but the swipe functionality is now working.

    ReplyDelete