one

I remember that when I first got into Android, I was interested in the special effects of contacts in the system. It would group contacts according to the first letter of their last name in the phone and always display a current group at the top of the interface. As shown below:

What interests me most is that when the latter group and the former group collide, there is a top squeeze animation. At that time, I thought of various ways to achieve this special effect, but limited to kung fu, all failed. Now more than two years have passed, AND I have grown up a lot. When I look back and think about this function, I suddenly find that I have an idea, so I immediately record it and share it with everyone.

First of all, the most important thing we need to know is SectionIndexer, which helps us control groups. Since SectionIndexer is an interface, you can customize a subclass to implement SectionIndexer, but writing an implementation of SectionIndexer would be too cumbersome, Here we directly use Android to provide a good implementation AlphabetIndexer, with it to achieve contact grouping function is enough.

The constructor of AlphabetIndexer takes three arguments, the first being cursor, the second being the sortedColumnIndex integer, and the third being the Alphabet string. The cursor is hand in the we found from the database cursor, sortedColumnIndex is pointed out which we are using sort by one column, and alphabet is specified the alphabet ordering rules, such as: “ABCDEFGHIJKLMNOPQRSTUVWXYZ.” With AlphabetIndexer, we can use its getPositionForSection and getSectionForPosition methods to find the current group and the current group’s position, This enables group navigation and squeeze animation similar to system contacts. For more details on AlphabetIndexer, please refer to the official documentation.

So how do we sort contacts? As mentioned earlier, there is a sortedColumnIndex parameter. Where is the sortedColumn? We look at the system contact raw_contacts this table (/ data/data/com. Android. Will the contacts/databases/contacts2 db), the table structure is more complex, there are more than 20 columns, There’s a column called sort_key, and that’s what we’re looking for! As shown below:

As you can see, this column is very user-friendly for us to record the pinyin corresponding to the Chinese characters, so that we can easily sort contacts by the value of this column.

Let’s start by creating a new Android project called ContactsDemo. To complete the layout file, open or create activity_main. XML as the main layout file for your application and add the following code:

<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"
    android:orientation="vertical" >
 
    <ListView
        android:id="@+id/contacts_list_view"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:fadingEdge="none" >
    </ListView>
    
     <LinearLayout
        android:id="@+id/title_layout"
        android:layout_width="fill_parent"
        android:layout_height="18dip"
        android:layout_alignParentTop="true"
        android:background="#303030" >
 
        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginLeft="10dip"
            android:textColor="#ffffff"
            android:textSize="13sp" />
    </LinearLayout>
 
</RelativeLayout>
Copy the code

The layout file is simple, with a ListView that displays contact information. Another LinearLayout is placed in the header, which contains a TextView that always displays the current group in the header of the interface.

two

Then create a new contact_item.xml layout for filling each line in the ListView as follows:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
 
    <LinearLayout
        android:id="@+id/sort_key_layout"
        android:layout_width="fill_parent"
        android:layout_height="18dip"
        android:background="#303030" >
 
        <TextView
            android:id="@+id/sort_key"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginLeft="10dip"
            android:textColor="#ffffff"
            android:textSize="13sp" />
    </LinearLayout>
 
    <LinearLayout
        android:id="@+id/name_layout"
        android:layout_width="fill_parent"
        android:layout_height="50dip" >
 
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="10dip"
            android:layout_marginRight="10dip"
            android:src="@drawable/icon" />
 
        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:textColor="#ffffff"
            android:textSize="22sp" />
    </LinearLayout>
 
</LinearLayout>
Copy the code

In this layout file, we first put the same group layout as we did before, because the group layout needs to be shown not only in the interface header, but also before the first element within each group. Then add a simple LinearLayout that contains an ImageView to display the contact picture and a TextView to display the contact name.

Now that our layout file is complete, it’s time to actually implement the functionality.

To start with, create a new Contact entity class:

Public class Contact {/** * private String name; /** * private String sortKey; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSortKey() { return sortKey; } public void setSortKey(String sortKey) { this.sortKey = sortKey; }}Copy the code

This entity class is simple, containing only the contact name and the sort key.

Create a ContactAdapter class that inherits from ArrayAdapter and add the following code:

Public class ContactAdapter extends ArrayAdapter<Contact> {/** * private int resource; /** * private SectionIndexer mIndexer; public ContactAdapter(Context context, int textViewResourceId, List<Contact> objects) { super(context, textViewResourceId, objects); resource = textViewResourceId; } @Override public View getView(int position, View convertView, ViewGroup parent) { Contact contact = getItem(position); LinearLayout layout = null; if (convertView == null) { layout = (LinearLayout) LayoutInflater.from(getContext()).inflate(resource, null); } else { layout = (LinearLayout) convertView; } TextView name = (TextView) layout.findViewById(R.id.name); LinearLayout sortKeyLayout = (LinearLayout) layout.findViewById(R.id.sort_key_layout); TextView sortKey = (TextView) layout.findViewById(R.id.sort_key); name.setText(contact.getName()); int section = mIndexer.getSectionForPosition(position); if (position == mIndexer.getPositionForSection(section)) { sortKey.setText(contact.getSortKey()); sortKeyLayout.setVisibility(View.VISIBLE); } else { sortKeyLayout.setVisibility(View.GONE); } return layout; } /** * passes a grouping tool to the current adapter. * * @param indexer */ public void setIndexer(SectionIndexer indexer) { mIndexer = indexer; }}Copy the code

The most important thing in the above code is the getView method. In this method, we use the getSectionForPosition method of SectionIndexer to retrieve the corresponding section value from the current position value. We then call getPositionForSection in the reverse direction of the section value to retrieve the new position value. If the current position value is equal to the new position value, then we can assume that the item in the current position is the first element under a group, and we should display the group layout. Otherwise, we should hide the group layout.

three

Finally, we write the main interface of the program, open or create MainActivity as the main interface of the program, the code is as follows:

Public class MainActivity extends Activity {/** * private LinearLayout titleLayout; /** * private TextView title; /** * contact ListView */ private ListView contactsListView; /** * contact list adapter */ private ContactAdapter adapter; /** * Private AlphabetIndexer Indexer; /** * Private List<Contact> contacts = new ArrayList<Contact>(); / * * * define the collation of the alphabet * / private String alphabet = "# ABCDEFGHIJKLMNOPQRSTUVWXYZ"; /** * the last visible element, used to record the identifier while scrolling. */ private int lastFirstVisibleItem = -1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); adapter = new ContactAdapter(this, R.layout.contact_item, contacts); titleLayout = (LinearLayout) findViewById(R.id.title_layout); title = (TextView) findViewById(R.id.title); contactsListView = (ListView) findViewById(R.id.contacts_list_view); Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI; Cursor cursor = getContentResolver().query(uri, new String[] { "display_name", "sort_key" }, null, null, "sort_key"); if (cursor.moveToFirst()) { do { String name = cursor.getString(0); String sortKey = getSortKey(cursor.getString(1)); Contact contact = new Contact(); contact.setName(name); contact.setSortKey(sortKey); contacts.add(contact); } while (cursor.moveToNext()); } startManagingCursor(cursor); indexer = new AlphabetIndexer(cursor, 1, alphabet); adapter.setIndexer(indexer); if (contacts.size() > 0) { setupContactsListView(); }} /** * Set the listener event for the contact ListView, according to the current slide state to change the group display position, so as to achieve the effect of squeeze animation. */ private void setupContactsListView() { contactsListView.setAdapter(adapter); contactsListView.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { int section = indexer.getSectionForPosition(firstVisibleItem); int nextSecPosition = indexer.getPositionForSection(section + 1); if (firstVisibleItem ! = lastFirstVisibleItem) { MarginLayoutParams params = (MarginLayoutParams) titleLayout.getLayoutParams(); params.topMargin = 0; titleLayout.setLayoutParams(params); title.setText(String.valueOf(alphabet.charAt(section))); } if (nextSecPosition == firstVisibleItem + 1) { View childView = view.getChildAt(0); if (childView ! = null) { int titleHeight = titleLayout.getHeight(); int bottom = childView.getBottom(); MarginLayoutParams params = (MarginLayoutParams) titleLayout .getLayoutParams(); if (bottom < titleHeight) { float pushedDistance = bottom - titleHeight; params.topMargin = (int) pushedDistance; titleLayout.setLayoutParams(params); } else { if (params.topMargin ! = 0) { params.topMargin = 0; titleLayout.setLayoutParams(params); } } } } lastFirstVisibleItem = firstVisibleItem; }}); } /** * get the first character of the sort key, if it is an English letter, otherwise return #. Private String getSortKey(String sortKeyString) {private String getSortKey(String sortKeyString) { String key = sortKeyString.substring(0, 1).toUpperCase(); if (key.matches("[A-Z]")) { return key; } return "#"; }}Copy the code

As you can see, in the onCreate method, we query the name and sort key of the contact from the system contact database, and pass the cursor returned by the query directly to AlphabetIndexer as the first parameter. The second sortedColumnIndex parameter is passed in as 1, since there are only two columns in total and the sorting key is in the second column. The third alphabet parameters introduced into “# ABCDEFGHIJKLMNOPQRSTUVWXYZ” string here, because there may be some the name of the contact person is beyond the scope of the alphabet, we unified using # to represent the part of the contact.

Then we listen to the ListView scroll in the setupContactsListView method, use the getSectionForPosition method in the onScroll method to get the group value of the first visible element, and add one to the group value. If the value of the first element in the next group is equal to the value of the first visible element plus 1, the layout of the next group will collide with the layout of the group at the top of the interface. After that, the ListView getChildAt(0) method is used to obtain the first sub-view displayed on the interface, and then view.getBottom is used to obtain the position of the bottom from the parent window, and the top group layout is offset vertically by comparing the height of the group layout. You can do the squeeze animation.

Androidmanifest.xml; android.permission.READ_CONTACTS; android.permission.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.contactsdemo"
    android:versionCode="1"
    android:version >
 
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="8" />
    
    <uses-permission android:></uses-permission>
 
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.NoTitleBar"
        >
        <activity
            android:
            android:label="@string/app_name" >
            <intent-filter>
                <action android: />
 
                <category android: />
            </intent-filter>
        </activity>
    </application>
 
</manifest>
Copy the code

Now let’s run the program, and it looks like this:

So far, the group navigation and squeeze animations have been done, and it looks and feels pretty good. In the next article, I will take you through the process of improving the program and adding alphabet scrolling.

Well, this is the end of today’s explanation, if you have questions, please leave a message below.

To download the source code, click here

Pay attention to my technical public account “Guo Lin”, there are high-quality technical articles pushed every week.