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

How Whatsapp stores its contacts in android phones?

Whatsapp stores its contacts in the ContactsContract.Data table. For each Whatsapp contact it stores two entries in the same table. 

The common fields for both records for each contact are:
  • account_type = "com.whatsapp"
  • contact_id = 123 (Same _ID in ContactsContract.Contacts table)
Now the fields which have separate values are:
1. ContactsContract.Data.MIMETYPE
  • "vnd.android.cursor.item/vnd.com.whatsapp.profile" (if the record contains phone number).
  • "vnd.android.cursor.item/name" (if the record contains contact name).
2. ContactsContract.Data.DATA3
  • contains either the name or the phone number depending on the ContactsContract.Data.MIMETYPE
this can be understood with the help of following diagram:

how whatsapp stores ites contacts in phonebook
How Whatsapp stores its contacts in Android Phonebook?
Okay! Want to know how to read these contacts? Have a look at this post.

Feel free to post your comments, queries and suggestions. Thanks