Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> Android實現聯系人分組導航和擠壓動畫

Android實現聯系人分組導航和擠壓動畫

編輯:Android開發實例

記得在我剛接觸Android的時候對系統聯系人中的特效很感興趣,它會根據手機中聯系人姓氏的首字母進行分組,並在界面的最頂端始終顯示一個當前的分組。如下圖所示:

                                             

最讓我感興趣的是,當後一個分組和前一個分組相碰時,會產生一個上頂的擠壓動畫。那個時候我思考了各種方法想去實現這種特效,可是限於功夫不到家,都未能成功。如今兩年多過去了,自己也成長了很多,再回頭去想想這個功能,突然發現已經有了思路,於是立刻記錄下來與大家分享。

首先講一下需要提前了解的知識點,這裡我們最需要用到的就是SectionIndexer,它能夠有效地幫助我們對分組進行控制。由於SectionIndexer是一個接口,你可以自定義一個子類來實現SectionIndexer,不過自己再寫一個SectionIndexer的實現太麻煩了,這裡我們直接使用Android提供好的實現AlphabetIndexer,用它來實現聯系人分組功能已經足夠了。
 

AlphabetIndexer的構造函數需要傳入三個參數,第一個參數是cursor,第二個參數是sortedColumnIndex整型,第三個參數是alphabet字符串。其中cursor就是把我們從數據庫中查出的游標傳進去,sortedColumnIndex就是指明我們是使用哪一列進行排序的,而alphabet則是指定字母表排序規則,比如:"ABCDEFGHIJKLMNOPQRSTUVWXYZ"。有了AlphabetIndexer,我們就可以通過它的getPositionForSection和getSectionForPosition方法,找出當前位置所在的分組,和當前分組所在的位置,從而實現類似於系統聯系人的分組導航和擠壓動畫效果,關於AlphabetIndexer更詳細的詳解,請參考官方文檔。
 

那麼我們應該怎樣對聯系人進行排序呢?前面也提到過,有一個sortedColumnIndex參數,這個sortedColumn到底在哪裡呢?我們來看一下系統聯系人的raw_contacts這張表(/data/data/com.android.providers.contacts/databases/contacts2.db),這個表結構比較復雜,裡面有二十多個列,其中有一列名叫sort_key,這就是我們要找的了!如下圖所示:
 

                                              
 

可以看到,這一列非常人性化地幫我們記錄了漢字所對應的拼音,這樣我們就可以通過這一列的值輕松為聯系人進行排序了。
 

下面我們就來開始實現,新建一個Android項目,命名為ContactsDemo。首先我們還是先來完成布局文件,打開或新建activity_main.xml作為程序的主布局文件,在裡面加入如下代碼:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  2.     xmlns:tools="http://schemas.android.com/tools" 
  3.     android:layout_width="match_parent" 
  4.     android:layout_height="match_parent" 
  5.     android:orientation="vertical" > 
  6.  
  7.     <ListView 
  8.         android:id="@+id/contacts_list_view" 
  9.         android:layout_width="fill_parent" 
  10.         android:layout_height="wrap_content" 
  11.         android:layout_alignParentTop="true" 
  12.         android:fadingEdge="none" > 
  13.     </ListView> 
  14.       
  15.      <LinearLayout 
  16.         android:id="@+id/title_layout" 
  17.         android:layout_width="fill_parent" 
  18.         android:layout_height="18dip" 
  19.         android:layout_alignParentTop="true" 
  20.         android:background="#303030" > 
  21.  
  22.         <TextView 
  23.             android:id="@+id/title" 
  24.             android:layout_width="wrap_content" 
  25.             android:layout_height="wrap_content" 
  26.             android:layout_gravity="center_horizontal" 
  27.             android:layout_marginLeft="10dip" 
  28.             android:textColor="#ffffff" 
  29.             android:textSize="13sp" /> 
  30.     </LinearLayout> 
  31.  
  32. </RelativeLayout> 

 

布局文件很簡單,裡面放入了一個ListView,用於展示聯系人信息。另外還在頭部放了一個LinearLayout,裡面包含了一個TextView,它的作用是在界面頭部始終顯示一個當前分組。

 

然後新建一個contact_item.xml的布局,這個布局用於在ListView中的每一行進行填充,代碼如下:

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  2.     android:layout_width="match_parent" 
  3.     android:layout_height="match_parent" 
  4.     android:orientation="vertical" > 
  5.  
  6.     <LinearLayout 
  7.         android:id="@+id/sort_key_layout" 
  8.         android:layout_width="fill_parent" 
  9.         android:layout_height="18dip" 
  10.         android:background="#303030" > 
  11.  
  12.         <TextView 
  13.             android:id="@+id/sort_key" 
  14.             android:layout_width="wrap_content" 
  15.             android:layout_height="wrap_content" 
  16.             android:layout_gravity="center_horizontal" 
  17.             android:layout_marginLeft="10dip" 
  18.             android:textColor="#ffffff" 
  19.             android:textSize="13sp" /> 
  20.     </LinearLayout> 
  21.  
  22.     <LinearLayout 
  23.         android:id="@+id/name_layout" 
  24.         android:layout_width="fill_parent" 
  25.         android:layout_height="50dip" > 
  26.  
  27.         <ImageView 
  28.             android:layout_width="wrap_content" 
  29.             android:layout_height="wrap_content" 
  30.             android:layout_gravity="center_vertical" 
  31.             android:layout_marginLeft="10dip" 
  32.             android:layout_marginRight="10dip" 
  33.             android:src="@drawable/icon" /> 
  34.  
  35.         <TextView 
  36.             android:id="@+id/name" 
  37.             android:layout_width="wrap_content" 
  38.             android:layout_height="wrap_content" 
  39.             android:layout_gravity="center_vertical" 
  40.             android:textColor="#ffffff" 
  41.             android:textSize="22sp" /> 
  42.     </LinearLayout> 
  43.  
  44. </LinearLayout> 



在這個布局文件中,首先是放入了一個和前面完成一樣的分組布局,因為不僅界面頭部需要展示分組,在每個分組內的第一個無素之前都需要展示分組布局。然後是加入一個簡單的LinearLayout,裡面包含了一個ImageView用於顯示聯系人頭像,還包含一個TextView用於顯示聯系人姓名。

 

這樣我們的布局文件就全部寫完了,下面開始來真正地實現功能。
 

先從簡單的開始,新建一個Contact實體類:

  1. public class Contact {  
  2.  
  3.     /**  
  4.      * 聯系人姓名  
  5.      */ 
  6.     private String name;  
  7.  
  8.     /**  
  9.      * 排序字母  
  10.      */ 
  11.     private String sortKey;  
  12.  
  13.     public String getName() {  
  14.         return name;  
  15.     }  
  16.  
  17.     public void setName(String name) {  
  18.         this.name = name;  
  19.     }  
  20.  
  21.     public String getSortKey() {  
  22.         return sortKey;  
  23.     }  
  24.  
  25.     public void setSortKey(String sortKey) {  
  26.         this.sortKey = sortKey;  
  27.     }  
  28.  
  29. }  

這個實體類很簡單,只包含了聯系人姓名和排序鍵。
 

 

接下來完成聯系人列表適配器的編寫,新建一個ContactAdapter類繼承自ArrayAdapter,加入如下代碼:

  1. public class ContactAdapter extends ArrayAdapter<Contact> {  
  2.  
  3.     /**  
  4.      * 需要渲染的item布局文件  
  5.      */ 
  6.     private int resource;  
  7.  
  8.     /**  
  9.      * 字母表分組工具  
  10.      */ 
  11.     private SectionIndexer mIndexer;  
  12.  
  13.     public ContactAdapter(Context context, int textViewResourceId, List<Contact> objects) {  
  14.         super(context, textViewResourceId, objects);  
  15.         resource = textViewResourceId;  
  16.     }  
  17.  
  18.     @Override 
  19.     public View getView(int position, View convertView, ViewGroup parent) {  
  20.         Contact contact = getItem(position);  
  21.         LinearLayout layout = null;  
  22.         if (convertView == null) {  
  23.             layout = (LinearLayout) LayoutInflater.from(getContext()).inflate(resource, null);  
  24.         } else {  
  25.             layout = (LinearLayout) convertView;  
  26.         }  
  27.         TextView name = (TextView) layout.findViewById(R.id.name);  
  28.         LinearLayout sortKeyLayout = (LinearLayout) layout.findViewById(R.id.sort_key_layout);  
  29.         TextView sortKey = (TextView) layout.findViewById(R.id.sort_key);  
  30.         name.setText(contact.getName());  
  31.         int section = mIndexer.getSectionForPosition(position);  
  32.         if (position == mIndexer.getPositionForSection(section)) {  
  33.             sortKey.setText(contact.getSortKey());  
  34.             sortKeyLayout.setVisibility(View.VISIBLE);  
  35.         } else {  
  36.             sortKeyLayout.setVisibility(View.GONE);  
  37.         }  
  38.         return layout;  
  39.     }  
  40.  
  41.     /**  
  42.      * 給當前適配器傳入一個分組工具。  
  43.      *   
  44.      * @param indexer  
  45.      */ 
  46.     public void setIndexer(SectionIndexer indexer) {  
  47.         mIndexer = indexer;  
  48.     }  
  49.  
  50. }  

上面的代碼中,最重要的就是getView方法,在這個方法中,我們使用SectionIndexer的getSectionForPosition方法,通過當前的position值拿到了對應的section值,然後再反向通過剛剛拿到的section值,調用getPositionForSection方法,取回新的position值。如果當前的position值和新的position值是相等的,那麼我們就可以認為當前position的項是某個分組下的第一個元素,我們應該將分組布局顯示出來,而其它的情況就應該將分組布局隱藏。

最後我們來編寫程序的主界面,打開或新建MainActivity作為程序的主界面,代碼如下所示:

  1. public class MainActivity extends Activity {  
  2.  
  3.     /**  
  4.      * 分組的布局  
  5.      */ 
  6.     private LinearLayout titleLayout;  
  7.  
  8.     /**  
  9.      * 分組上顯示的字母  
  10.      */ 
  11.     private TextView title;  
  12.  
  13.     /**  
  14.      * 聯系人ListView  
  15.      */ 
  16.     private ListView contactsListView;  
  17.  
  18.     /**  
  19.      * 聯系人列表適配器  
  20.      */ 
  21.     private ContactAdapter adapter;  
  22.  
  23.     /**  
  24.      * 用於進行字母表分組  
  25.      */ 
  26.     private AlphabetIndexer indexer;  
  27.  
  28.     /**  
  29.      * 存儲所有手機中的聯系人  
  30.      */ 
  31.     private List<Contact> contacts = new ArrayList<Contact>();  
  32.  
  33.     /**  
  34.      * 定義字母表的排序規則  
  35.      */ 
  36.     private String alphabet = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ";  
  37.  
  38.     /**  
  39.      * 上次第一個可見元素,用於滾動時記錄標識。  
  40.      */ 
  41.     private int lastFirstVisibleItem = -1;  
  42.  
  43.     @Override 
  44.     protected void onCreate(Bundle savedInstanceState) {  
  45.         super.onCreate(savedInstanceState);  
  46.         setContentView(R.layout.activity_main);  
  47.         adapter = new ContactAdapter(this, R.layout.contact_item, contacts);  
  48.         titleLayout = (LinearLayout) findViewById(R.id.title_layout);  
  49.         title = (TextView) findViewById(R.id.title);  
  50.         contactsListView = (ListView) findViewById(R.id.contacts_list_view);  
  51.         Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;  
  52.         Cursor cursor = getContentResolver().query(uri,  
  53.                 new String[] { "display_name", "sort_key" }, null, null, "sort_key");  
  54.         if (cursor.moveToFirst()) {  
  55.             do {  
  56.                 String name = cursor.getString(0);  
  57.                 String sortKey = getSortKey(cursor.getString(1));  
  58.                 Contact contact = new Contact();  
  59.                 contact.setName(name);  
  60.                 contact.setSortKey(sortKey);  
  61.                 contacts.add(contact);  
  62.             } while (cursor.moveToNext());  
  63.         }  
  64.         startManagingCursor(cursor);  
  65.         indexer = new AlphabetIndexer(cursor, 1, alphabet);  
  66.         adapter.setIndexer(indexer);  
  67.         if (contacts.size() > 0) {  
  68.             setupContactsListView();  
  69.         }  
  70.     }  
  71.  
  72.     /**  
  73.      * 為聯系人ListView設置監聽事件,根據當前的滑動狀態來改變分組的顯示位置,從而實現擠壓動畫的效果。  
  74.      */ 
  75.     private void setupContactsListView() {  
  76.         contactsListView.setAdapter(adapter);  
  77.         contactsListView.setOnScrollListener(new OnScrollListener() {  
  78.             @Override 
  79.             public void onScrollStateChanged(AbsListView view, int scrollState) {  
  80.             }  
  81.  
  82.             @Override 
  83.             public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,  
  84.                     int totalItemCount) {  
  85.                 int section = indexer.getSectionForPosition(firstVisibleItem);  
  86.                 int nextSecPosition = indexer.getPositionForSection(section + 1);  
  87.                 if (firstVisibleItem != lastFirstVisibleItem) {  
  88.                     MarginLayoutParams params = (MarginLayoutParams) titleLayout.getLayoutParams();  
  89.                     params.topMargin = 0;  
  90.                     titleLayout.setLayoutParams(params);  
  91.                     title.setText(String.valueOf(alphabet.charAt(section)));  
  92.                 }  
  93.                 if (nextSecPosition == firstVisibleItem + 1) {  
  94.                     View childView = view.getChildAt(0);  
  95.                     if (childView != null) {  
  96.                         int titleHeight = titleLayout.getHeight();  
  97.                         int bottom = childView.getBottom();  
  98.                         MarginLayoutParams params = (MarginLayoutParams) titleLayout  
  99.                                 .getLayoutParams();  
  100.                         if (bottom < titleHeight) {  
  101.                             float pushedDistance = bottom - titleHeight;  
  102.                             params.topMargin = (int) pushedDistance;  
  103.                             titleLayout.setLayoutParams(params);  
  104.                         } else {  
  105.                             if (params.topMargin != 0) {  
  106.                                 params.topMargin = 0;  
  107.                                 titleLayout.setLayoutParams(params);  
  108.                             }  
  109.                         }  
  110.                     }  
  111.                 }  
  112.                 lastFirstVisibleItem = firstVisibleItem;  
  113.             }  
  114.         });  
  115.  
  116.     }  
  117.  
  118.     /**  
  119.      * 獲取sort key的首個字符,如果是英文字母就直接返回,否則返回#。  
  120.      *   
  121.      * @param sortKeyString  
  122.      *            數據庫中讀取出的sort key  
  123.      * @return 英文字母或者#  
  124.      */ 
  125.     private String getSortKey(String sortKeyString) {  
  126.         String key = sortKeyString.substring(0, 1).toUpperCase();  
  127.         if (key.matches("[A-Z]")) {  
  128.             return key;  
  129.         }  
  130.         return "#";  
  131.     }  
  132.  
  133. }  

可以看到,在onCreate方法中,我們從系統聯系人數據庫中去查詢聯系人的姓名和排序鍵,之後將查詢返回的cursor直接傳入AlphabetIndexer作為第一個參數。由於我們一共就查了兩列,排序鍵在第二列,所以我們第二個sortedColumnIndex參數傳入1。第三個alphabet參數這裡傳入了"#ABCDEFGHIJKLMNOPQRSTUVWXYZ"字符串,因為可能有些聯系人的姓名不在字母表范圍內,我們統一用#來表示這部分聯系人。

然後我們在setupContactsListView方法中監聽了ListView的滾動,在onScroll方法中通過getSectionForPosition方法獲取第一個可見元素的分組值,然後給該分組值加1,再通過getPositionForSection方法或者到下一個分組中的第一個元素,如果下個分組的第一個元素值等於第一個可見元素的值加1,那就說明下個分組的布局要和界面頂部分組布局相碰了。之後再通過ListView的getChildAt(0)方法,獲取到界面上顯示的第一個子View,再用view.getBottom獲取底部距離父窗口的位置,對比分組布局的高度來對頂部分組布局進行縱向偏移,就可以實現擠壓動畫的效果了。

 

最後給出AndroidManifest.xml的代碼,由於要讀取手機聯系人,因此需要加上android.permission.READ_CONTACTS的聲明:

  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android" 
  2.     package="com.example.contactsdemo" 
  3.     android:versionCode="1" 
  4.     android:versionName="1.0" > 
  5.  
  6.     <uses-sdk 
  7.         android:minSdkVersion="8" 
  8.         android:targetSdkVersion="8" /> 
  9.       
  10.     <uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission> 
  11.  
  12.     <application 
  13.         android:allowBackup="true" 
  14.         android:icon="@drawable/ic_launcher" 
  15.         android:label="@string/app_name" 
  16.         android:theme="@android:style/Theme.NoTitleBar" 
  17.         > 
  18.         <activity 
  19.             android:name="com.example.contactsdemo.MainActivity" 
  20.             android:label="@string/app_name" > 
  21.             <intent-filter> 
  22.                 <action android:name="android.intent.action.MAIN" /> 
  23.  
  24.                 <category android:name="android.intent.category.LAUNCHER" /> 
  25.             </intent-filter> 
  26.         </activity> 
  27.     </application> 
  28.  
  29. </manifest> 

現在我們來運行一下程序,效果如下圖所示:

                                               

目前的話,分組導航和擠壓動畫效果都已經完成了,看起來感覺還是挺不錯的,下一篇文章我會帶領大家繼續完善這個程序,加入字母表快速滾動功能,感興趣的朋友請繼續閱讀後面的文章。

源碼下載,請點擊這裡

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved