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

March 5, 2014

How to read WhatsApp contacts in your android app using Content Providers?

Today everyone wants to make their App as much socially integrated as possible. WhatsApp is the most popular messaging app available out there in the market but unfortunately WhatsApp does not provide any APIs for the integration of other apps. But most people want to enable their apps to send messages via Whatsapp ( i.e. directly; not via implicit intents or choosers).

We all know that Whatsapp stores its contacts in the phone's contact book. Hence if the Whatsapp contacts are stored in the contact book then other apps should be able to view them. Whatsapp doesn't have its own Content Provider but because the contacts are stored in the phone then we should be able to view them with the help of ContactsContract content provider.

Now different people may have different ways of accessing the Whatsapp contacts and there may be better ways of doing what I am trying to do but here is what I did to do the same.

How Whatsapp stores contacts in android phones?

To understand how whatsapp organizes ites contacts go here.

First we will need the following permission in the AndroidManifest.xml in order to read these contacts:

<uses-permission android:name="android.permission.READ_CONTACTS" />

I am showing all the Whatsapp contacts in a custom ListView with two TextViews. Touching on any item in this list view will open the chat thread for that contact.

whatsapp_list_item.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?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="match_parent"
    android:orientation="vertical" >
    
    <TextView 
        android:id="@+id/txtName"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        style="?android:attr/textAppearanceMedium" />
    
    <TextView 
        android:id="@+id/txtNumber"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content" />

</LinearLayout>

activity_whats_app_contacts.xml


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<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=".WhatsAppContacts" >

    <ListView
        android:id="@+id/listWhatsAppContacts"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

WhatsAppContacts.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
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.SimpleAdapter;
import android.widget.Toast;

public class WhatsAppContacts extends Activity {

    private ArrayList<Map<String, String>> contacts;
    private ListView contactsListView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_whats_app_contacts);
        
        contactsListView = (ListView) findViewById(R.id.listWhatsAppContacts);
        
        // Create a progress bar to display while the list loads
        ProgressBar progressBar = new ProgressBar(this);
        progressBar.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
                LayoutParams.WRAP_CONTENT));
        progressBar.setIndeterminate(true);
        contactsListView.setEmptyView(progressBar);

        // Must add the progress bar to the root of the layout
        ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
        root.addView(progressBar);
        
        String[] from = { "name" , "number" };
        int[] to = { R.id.txtName, R.id.txtNumber };
        
        contacts = fetchWhatsAppContacts();
        
        SimpleAdapter adapter = new SimpleAdapter(this, contacts, R.layout.whatsapp_list_item, from, to);
        contactsListView.setAdapter(adapter);
        
        contactsListView.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
                    long arg3) {
                try{
                    Uri uri = Uri.parse("smsto:"+ contacts.get(arg2).get("number").toString());
                    Intent i = new Intent(Intent.ACTION_SENDTO, uri);
                    i.setPackage("com.whatsapp");
                    startActivity(i);
                }catch (ActivityNotFoundException e) {
                    Toast.makeText(getApplicationContext(), "no whatsapp!", Toast.LENGTH_SHORT).show();
                    Log.e("Intent", e.getMessage());
                }
            }
        });
        
    }
    
    private HashMap<String, String> putData(String name, String number) {
        HashMap<String, String> item = new HashMap<String, String>();
        item.put("name", name);
        item.put("number", number);
        return item;
      }
    
    private ArrayList<Map<String, String>> fetchWhatsAppContacts(){
        
        ArrayList<Map<String, String>> list = new ArrayList<Map<String,String>>();
        
        final String[] projection={
                ContactsContract.Data.CONTACT_ID,
                ContactsContract.Data.MIMETYPE,
                "account_type",
                ContactsContract.Data.DATA3,
                };
        final String selection= ContactsContract.Data.MIMETYPE+" =? and account_type=?";
        final String[] selectionArgs = {
                "vnd.android.cursor.item/vnd.com.whatsapp.profile",
                "com.whatsapp"
                };
        ContentResolver cr = getContentResolver();
        Cursor c = cr.query(
                ContactsContract.Data.CONTENT_URI,
                projection,
                selection,
                selectionArgs,
                null);
        while(c.moveToNext()){
            String id=c.getString(c.getColumnIndex(ContactsContract.Data.CONTACT_ID));
            String number=c.getString(c.getColumnIndex(ContactsContract.Data.DATA3));
            String name="";
            Cursor mCursor=getContentResolver().query(
                    ContactsContract.Contacts.CONTENT_URI,
                    new String[]{ContactsContract.Contacts.DISPLAY_NAME},
                    ContactsContract.Contacts._ID+" =?",
                    new String[]{id},
                    null);
            while(mCursor.moveToNext()){
                name=mCursor.getString(mCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
            }
            mCursor.close();
            list.add(putData(name, number));
        }
        Log.v("WhatsApp", "Total WhatsApp Contacts: "+c.getCount());
        c.close();
        return list;
    }

}


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