1、概述
最近由于项目需求,需要做一个查看手机通讯录,并且取出相应的数据。类似于下图:
用到的一个主要的知识点:SectionIndexer——能够有效地帮助我们对分组进行控制,由于SectionIndexer是一个接口,你可以自定义一个子类来实现SectionIndexer,
不过自己再写一个SectionIndexer的实现太麻烦了,这里我们直接使用Android提供好
的实现AlphabetIndexer,用它来实现联系人分组功能已经足够了。AlphabetIndexer的构造函数需要传入三个参数,第一个参数是cursor,
第二个参数是sortedColumnIndex整型,第三个参数是alphabet字符串。
其中cursor就是把我们从数据库中查出的游标传进去,
sortedColumnIndex就是指明我们是使用哪一列进行排序的,
而alphabet则是指定字母表排序规则,比如:"ABCDEFGHIJKLMNOPQRSTUVWXYZ"。
有了AlphabetIndexer,我们就可以通过它的getPositionForSection和getSectionForPosition方法,
找出当前位置所在的分组,和当前分组所在的位置,
从而实现类似于系统联系人的分组导航和挤压动画效果。
2、效果图
大家可以清晰的看到,滑动界面,右侧的指示也会随着页面的变换而变换,
按住右侧,是按照字母来查询,由于这里是使用的genymotion,不能输入中文汉字,
所以都是英文的联系人,中文同样的效果。点击查询,字母的显示条就会消失,
这里使用的是模糊查询,只要名字中有你输入的关键字都会显示出来。
3、实现
下面我们就来开始实现,新建一个Android项目,命名为Contacts。首先我们还是先来完成布局文件,打开或新建activity_main.xml作为程序的主布局文件,代码会长一点,由于要实现右侧的A-Z的滑动栏,中间有一大部分的代码是类似的,这里就不贴出来了,感兴趣的朋友可以下载整个项目。
然后新建一个contact_item.xml的布局,这个布局用于在ListView中的每一行进行填充,代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="#ffffff"> <LinearLayout android:id="@+id/sort_key_layout" android:layout_width="fill_parent" android:layout_height="18dip" android:layout_marginRight="15dp" android:background="#64a300"> <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="60dip" android:orientation="vertical"> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="0dp" android:layout_weight="1" android:gravity="center_vertical" android:textColor="#303030" android:layout_marginLeft="15dp" android:text="李三" android:textSize="20sp" /> <TextView android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginLeft="15dp" android:layout_marginRight="15dp" android:background="#4064a300" /> </LinearLayout> </LinearLayout>
在这个布局文件中,首先是放入了一个和前面完成一样的分组布局,因为不仅界面头部需要展示分组,在每个分组内的第一个无素之前都需要展示分组布局。然后是加入一个简单的LinearLayout,包含一个TextView用于显示联系人姓名。
这样我们的布局文件就全部写完了,下面开始来真正地实现功能。
先从简单的开始,新建一个PlayerInfo实体类:
package com.example.contactsdemo; import java.io.Serializable; /* * 用户信息 * auth:liyachao * date:2015/4/6 */ public class PlayerInfo { private String playerName; private String playerPhone; /** * 排序字母 */ private String sortKey; public String getSortKey() { return sortKey; } public void setSortKey(String sortKey) { this.sortKey = sortKey; } public String getPlayerPhone() { return playerPhone; } public void setPlayerPhone(String playerPhone) { this.playerPhone = playerPhone; } public String getPlayerName() { return playerName; } public void setPlayerName(String playerName) { this.playerName = playerName; } }
这个实体类很简单,只包含了联系人姓名、排序键和联系人电话。
接下来完成联系人列表适配器的编写,新建一个ContactAdapter类继承自ArrayAdapter,加入如下代码:
package com.example.contactsdemo; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.LinearLayout; import android.widget.SectionIndexer; import android.widget.TextView; import java.util.List; /** * 联系人列表适配器。 * * @author guolin */ public class ContactAdapter extends BaseAdapter { /** * 需要渲染的item布局文件 */ private int resource; private Context context; private List<PlayerInfo> players; private boolean flag = true; /** * 字母表分组工具 */ private SectionIndexer mIndexer; public ContactAdapter(Context context, int textViewResourceId, List<PlayerInfo> players) { resource = textViewResourceId; this.context = context; this.players = players; } @Override public int getCount() { return players.size(); } @Override public PlayerInfo getItem(int position) { return players.get(position); } @Override public long getItemId(int position) { return position; } public void dataChanged(List<PlayerInfo> players) { this.players = players; notifyDataSetChanged(); flag = false; } @Override public View getView(int position, View convertView, ViewGroup parent) { PlayerInfo contact = getItem(position); LinearLayout layout = null; if (convertView == null) { layout = (LinearLayout) LayoutInflater.from(context).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.getPlayerName()); if (flag) { int section = mIndexer.getSectionForPosition(position); if (position == mIndexer.getPositionForSection(section)) { sortKey.setText(contact.getSortKey()); sortKeyLayout.setVisibility(View.VISIBLE); } else { sortKeyLayout.setVisibility(View.GONE); } } else { sortKeyLayout.setVisibility(View.GONE); } return layout; } /** * 给当前适配器传入一个分组工具。 * * @param indexer */ public void setIndexer(SectionIndexer indexer) { mIndexer = indexer; flag = true; } }
上面的代码中,最重要的就是getView方法,在这个方法中,我们首先判断是查找类型还是滑动页面类型,如果是查找类型,我们将头部的指示条就隐藏,如果不是再做判断,使用SectionIndexer的getSectionForPosition方法,通过当前的position值拿到了对应的section值,然后再反向通过刚刚拿到的section值,调用getPositionForSection方法,取回新的position值。如果当前的position值和新的position值是相等的,那么我们就可以认为当前position的项是某个分组下的第一个元素,我们应该将分组布局显示出来。
最后我们来编写程序的主界面,打开或新建MainActivity作为程序的主界面,代码如下所示:
package com.example.contactsdemo; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.content.ContentResolver; import android.database.Cursor; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AlphabetIndexer; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; /** * chang date:2015/4/5 by liyachao */ public class MainActivity extends Activity { private ListView lv; private EditText edittext; /** * 联系人总人数 */ private int contactNumber; /** * 分组的布局 */ private LinearLayout titleLayout; /** * 分组上显示的字母 */ private TextView title; private ArrayList<PlayerInfo> allPlayerInfos; /** * 联系人列表适配器 */ private ContactAdapter adapter; /** * 用于进行字母表分组 */ private AlphabetIndexer indexer; private RelativeLayout sectionToastLayout; private TextView sectionToastText; private LinearLayout alphabetLayout; /** * A-Z的集合 */ private List<TextView> alphabetList; /** * 定义字母表的排序规则 */ private String alphabet = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private int lastFirstVisibleItem = -1; /** * 电话号码* */ private static final int PHONES_NUMBER_INDEX = 1; /** * 联系人显示名称* */ private static final int PHONES_DISPLAY_NAME_INDEX = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setControl(); } private void setControl() { alphabetList = new ArrayList<TextView>(); setAlphabetData(); titleLayout = (LinearLayout) findViewById(R.id.title_layout1); title = (TextView) findViewById(R.id.title); sectionToastLayout = (RelativeLayout) findViewById(R.id.section_toast_layout); sectionToastText = (TextView) findViewById(R.id.section_toast_text); alphabetLayout = (LinearLayout) findViewById(R.id.alphabet_layout); lv = (ListView) findViewById(R.id.lv_select_contact); edittext = (EditText) findViewById(R.id.edittext); allPlayerInfos = getContactInfos(); adapter = new ContactAdapter(this, R.layout.contact_item, allPlayerInfos); edittext.setHint("搜索" + contactNumber + "位联系人"); adapter.setIndexer(indexer); if (allPlayerInfos.size() > 0) { setupContactsListView(); setAlpabetListener(); } /** * 向listview设置点击事件 */ lv.setOnItemClickListener(new MyOnItemClickListener()); /** * 向edittext设置监听事件 */ edittext.addTextChangedListener(new MyTextWatcher()); } /** * 根据填写的关键字在电话簿里寻找相关信息 * * @param name * @return */ private ArrayList<PlayerInfo> searchItem(String name) { ArrayList<PlayerInfo> mSearchList = new ArrayList<PlayerInfo>(); for (int i = 0; i < allPlayerInfos.size(); i++) { int index = allPlayerInfos.get(i).getPlayerName().indexOf(name); // 存在匹配的数据 if (index != -1) { mSearchList.add(allPlayerInfos.get(i)); } } return mSearchList; } /** * 获取系统的所有的联系人信息. * * @return */ public ArrayList<PlayerInfo> getContactInfos() { allPlayerInfos = new ArrayList<PlayerInfo>(); ContentResolver resolver = getContentResolver(); Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI; Cursor phoneCursor = resolver.query(uri, new String[]{Phone.DISPLAY_NAME, Phone.NUMBER, Phone.SORT_KEY_PRIMARY} , null, null, Phone.SORT_KEY_PRIMARY); if (phoneCursor.moveToFirst()) { do { //得到手机号码 String phoneNumber = phoneCursor.getString(PHONES_NUMBER_INDEX); String sortKey = getSortKey(phoneCursor.getString(2)); //得到联系人名称 String contactName = phoneCursor.getString(PHONES_DISPLAY_NAME_INDEX); PlayerInfo playerInfo = new PlayerInfo(); playerInfo.setPlayerName(contactName); playerInfo.setPlayerPhone(phoneNumber); playerInfo.setSortKey(sortKey); allPlayerInfos.add(playerInfo); } while (phoneCursor.moveToNext()); } contactNumber = allPlayerInfos.size(); startManagingCursor(phoneCursor); indexer = new AlphabetIndexer(phoneCursor, 2, alphabet); return allPlayerInfos; } /** * 获取sort key的首个字符,如果是英文字母就直接返回,否则返回#。 * * @param sortKeyString 数据库中读取出的sort key * @return 英文字母或者# */ private String getSortKey(String sortKeyString) { alphabetLayout.getHeight(); String key = sortKeyString.substring(0, 1).toUpperCase(); if (key.matches("[A-Z]")) { return key; } return "#"; } /** * 为联系人ListView设置监听事件, * 根据当前的滑动状态来改变分组的显示位置, * 从而实现挤压动画的效果。 * auth:liyachao */ private void setupContactsListView() { lv.setAdapter(adapter); lv.setOnScrollListener(new AbsListView.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); setSortAlphabet(section); if (firstVisibleItem != lastFirstVisibleItem) { ViewGroup.MarginLayoutParams params = (ViewGroup.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(); ViewGroup.MarginLayoutParams params = (ViewGroup.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; } }); } /** * 设置默认的字幕背景和字体颜色 */ private void setSortAlphabet(int section) { TextView tv; for (int i = 0; i < 27; i++) { if (section == i) { tv = alphabetList.get(section); tv.setTextColor(Color.parseColor("#ffffff")); tv.setBackgroundResource(R.drawable.text_bg_frame); } else { tv = alphabetList.get(i); tv.setTextColor(Color.parseColor("#303030")); tv.setBackgroundColor(Color.parseColor("#ffffff")); } tv.setGravity(Gravity.CENTER); } } /** * 设置字母表上的触摸事件,根据当前触摸的位置结合字母表的高度, * 计算出当前触摸在哪个字母上。 * 当手指按在字母表上时,展示弹出式分组。手指离开字母表时, * 将弹出式分组隐藏。 * auth:liyachao */ private void setAlpabetListener() { alphabetLayout.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { float alphabetHeight = alphabetLayout.getHeight(); float y = event.getY(); int sectionPosition = (int) ((y / alphabetHeight) / (1f / 27f)); if (sectionPosition < 0) { sectionPosition = 0; } else if (sectionPosition > 26) { sectionPosition = 26; } String sectionLetter = String.valueOf(alphabet.charAt(sectionPosition)); int position = indexer.getPositionForSection(sectionPosition); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: alphabetList.get(sectionPosition).setTextColor(Color.parseColor("#ffffff")); alphabetList.get(sectionPosition).setBackgroundResource(R.drawable.text_bg_frame); sectionToastLayout.setVisibility(View.VISIBLE); sectionToastText.setText(sectionLetter); lv.setSelection(position); break; case MotionEvent.ACTION_MOVE: setSortAlphabet(sectionPosition); sectionToastText.setText(sectionLetter); lv.setSelection(position); break; default: setSortAlphabet(sectionPosition); sectionToastLayout.setVisibility(View.GONE); } return true; } }); } class MyOnItemClickListener implements OnItemClickListener { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String name = ((PlayerInfo) parent.getItemAtPosition(position)).getPlayerName() + ""; String phone = ((PlayerInfo) parent.getItemAtPosition(position)).getPlayerPhone() + ""; String str = name + "的电话为: " + phone; Toast.makeText(MainActivity.this, str, Toast.LENGTH_LONG).show(); } } class MyTextWatcher implements TextWatcher { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { String str = s.toString(); if (str.equals("")) { adapter.dataChanged(allPlayerInfos); adapter.setIndexer(indexer); titleLayout.setVisibility(View.VISIBLE); } else { Log.i("tag", "name111: " + str); ArrayList<PlayerInfo> temp; temp = searchItem(str.trim()); adapter.dataChanged(temp); titleLayout.setVisibility(View.GONE); } lv.setAdapter(adapter); } } private void setAlphabetData() { TextView jing = (TextView) findViewById(R.id.contact_); alphabetList.add(jing); TextView A = (TextView) findViewById(R.id.contact_A); alphabetList.add(A); TextView B = (TextView) findViewById(R.id.contact_B); alphabetList.add(B); TextView C = (TextView) findViewById(R.id.contact_C); alphabetList.add(C); TextView D = (TextView) findViewById(R.id.contact_D); alphabetList.add(D); TextView E = (TextView) findViewById(R.id.contact_E); alphabetList.add(E); TextView F = (TextView) findViewById(R.id.contact_F); alphabetList.add(F); TextView G = (TextView) findViewById(R.id.contact_G); alphabetList.add(G); TextView H = (TextView) findViewById(R.id.contact_H); alphabetList.add(H); TextView I = (TextView) findViewById(R.id.contact_I); alphabetList.add(I); TextView J = (TextView) findViewById(R.id.contact_J); alphabetList.add(J); TextView K = (TextView) findViewById(R.id.contact_K); alphabetList.add(K); TextView L = (TextView) findViewById(R.id.contact_L); alphabetList.add(L); TextView M = (TextView) findViewById(R.id.contact_M); alphabetList.add(M); TextView N = (TextView) findViewById(R.id.contact_N); alphabetList.add(N); TextView O = (TextView) findViewById(R.id.contact_O); alphabetList.add(O); TextView P = (TextView) findViewById(R.id.contact_P); alphabetList.add(P); TextView Q = (TextView) findViewById(R.id.contact_Q); alphabetList.add(Q); TextView R1 = (TextView) findViewById(R.id.contact_R); alphabetList.add(R1); TextView S = (TextView) findViewById(R.id.contact_S); alphabetList.add(S); TextView T = (TextView) findViewById(R.id.contact_T); alphabetList.add(T); TextView U = (TextView) findViewById(R.id.contact_U); alphabetList.add(U); TextView V = (TextView) findViewById(R.id.contact_V); alphabetList.add(V); TextView W = (TextView) findViewById(R.id.contact_W); alphabetList.add(W); TextView X = (TextView) findViewById(R.id.contact_X); alphabetList.add(X); TextView Y = (TextView) findViewById(R.id.contact_Y); alphabetList.add(Y); TextView Z = (TextView) findViewById(R.id.contact_Z); alphabetList.add(Z); } }
最后要记住,要在AndroidManifest.xml给出读取手机联系人的权限声明:
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>