转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9033553

记得在我刚接触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作为程序的主布局文件,在里面加入如下代码:

<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" ><ListViewandroid:id="@+id/contacts_list_view"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_alignParentTop="true"android:fadingEdge="none" ></ListView><LinearLayoutandroid:id="@+id/title_layout"android:layout_width="fill_parent"android:layout_height="18dip"android:layout_alignParentTop="true"android:background="#303030" ><TextViewandroid: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>

布局文件很简单,里面放入了一个ListView,用于展示联系人信息。另外还在头部放了一个LinearLayout,里面包含了一个TextView,它的作用是在界面头部始终显示一个当前分组。

然后新建一个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" ><LinearLayoutandroid:id="@+id/sort_key_layout"android:layout_width="fill_parent"android:layout_height="18dip"android:background="#303030" ><TextViewandroid: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><LinearLayoutandroid:id="@+id/name_layout"android:layout_width="fill_parent"android:layout_height="50dip" ><ImageViewandroid: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" /><TextViewandroid: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>

在这个布局文件中,首先是放入了一个和前面完成一样的分组布局,因为不仅界面头部需要展示分组,在每个分组内的第一个无素之前都需要展示分组布局。然后是加入一个简单的LinearLayout,里面包含了一个ImageView用于显示联系人头像,还包含一个TextView用于显示联系人姓名。

这样我们的布局文件就全部写完了,下面开始来真正地实现功能。

先从简单的开始,新建一个Contact实体类:

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;}}

这个实体类很简单,只包含了联系人姓名和排序键。

接下来完成联系人列表适配器的编写,新建一个ContactAdapter类继承自ArrayAdapter,加入如下代码:

public class ContactAdapter extends ArrayAdapter<Contact> {/*** 需要渲染的item布局文件*/private int resource;/*** 字母表分组工具*/private SectionIndexer mIndexer;public ContactAdapter(Context context, int textViewResourceId, List<Contact> objects) {super(context, textViewResourceId, objects);resource = textViewResourceId;}@Overridepublic 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;}/*** 给当前适配器传入一个分组工具。* * @param indexer*/public void setIndexer(SectionIndexer indexer) {mIndexer = indexer;}}

上面的代码中,最重要的就是getView方法,在这个方法中,我们使用SectionIndexer的getSectionForPosition方法,通过当前的position值拿到了对应的section值,然后再反向通过刚刚拿到的section值,调用getPositionForSection方法,取回新的position值。如果当前的position值和新的position值是相等的,那么我们就可以认为当前position的项是某个分组下的第一个元素,我们应该将分组布局显示出来,而其它的情况就应该将分组布局隐藏。

最后我们来编写程序的主界面,打开或新建MainActivity作为程序的主界面,代码如下所示:

public class MainActivity extends Activity {/*** 分组的布局*/private LinearLayout titleLayout;/*** 分组上显示的字母*/private TextView title;/*** 联系人ListView*/private ListView contactsListView;/*** 联系人列表适配器*/private ContactAdapter adapter;/*** 用于进行字母表分组*/private AlphabetIndexer indexer;/*** 存储所有手机中的联系人*/private List<Contact> contacts = new ArrayList<Contact>();/*** 定义字母表的排序规则*/private String alphabet = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ";/*** 上次第一个可见元素,用于滚动时记录标识。*/private int lastFirstVisibleItem = -1;@Overrideprotected 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();}}/*** 为联系人ListView设置监听事件,根据当前的滑动状态来改变分组的显示位置,从而实现挤压动画的效果。*/private void setupContactsListView() {contactsListView.setAdapter(adapter);contactsListView.setOnScrollListener(new OnScrollListener() {@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {}@Overridepublic 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;}});}/*** 获取sort key的首个字符,如果是英文字母就直接返回,否则返回#。* * @param sortKeyString*            数据库中读取出的sort key* @return 英文字母或者#*/private String getSortKey(String sortKeyString) {String key = sortKeyString.substring(0, 1).toUpperCase();if (key.matches("[A-Z]")) {return key;}return "#";}}

可以看到,在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的声明:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.contactsdemo"android:versionCode="1"android:versionName="1.0" ><uses-sdkandroid:minSdkVersion="8"android:targetSdkVersion="8" /><uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission><applicationandroid:allowBackup="true"android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:theme="@android:style/Theme.NoTitleBar"><activityandroid:name="com.example.contactsdemo.MainActivity"android:label="@string/app_name" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>

现在我们来运行一下程序,效果如下图所示:

目前的话,分组导航和挤压动画效果都已经完成了,看起来感觉还是挺不错的,下一篇文章我会带领大家继续完善这个程序,加入字母表快速滚动功能,感兴趣的朋友请继续阅读Android系统联系人全特效实现(下),字母表快速滚动。

好了,今天的讲解到此结束,有疑问的朋友请在下面留言。

源码下载,请点击这里

Android系统联系人全特效实现(上),分组导航和挤压动画相关推荐

  1. Android系统联系人全特效实现(下),字母表快速滚动

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9050671 在上一篇文章中,我和大家一起实现了类似于Android系统联系人的分组 ...

  2. 【Android 逆向】Android 系统中文件的用户和分组 ( 文件所有者与分组 | /sdcard/ 的文件分组 | /data/ 目录分析 | 用户类型 )

    文章目录 一.文件所有者与分组 二./sdcard/ 的文件分组 三./data/ 目录分析 四.用户类型 一.文件所有者与分组 使用 ls -l 命令 , 查看 Android 系统根目录 , 下图 ...

  3. 关于Android系统中“你的手机上未安装应用程序”问题

    有时候我们把写好的APK安装到Android系统中后,点击应用程序图标后程序并不运行,却提示"你的手机上未安装应用程序",这个问题多半是AndroidManifest.xml文件不 ...

  4. Android系统的微单,史上最强大Android系统无反相机:三星Galaxy NX数码相机

    三星Galaxy NX数码相机 Galaxy NX的本质是一台相机,而不是一部手机,其定位也要比Galaxy S4 Zoom更高端一些,Galaxy NX配备了取景器.弹出式闪光灯.热靴和对焦辅助灯, ...

  5. Android布局延伸状态栏,Android沉浸式全屏讲解(状态栏、导航栏处理)

    Android应用中经常会有一些要求全屏显隐状态栏导航栏的需求.通过全屏沉浸式的处理可以让应用达到更好的显示效果.下面系统的讲解一下有关全屏,隐藏状态栏导航栏,沉浸式的知识. 在Android4.1之 ...

  6. android 沉浸式菜单栏,Android沉浸式全屏讲解(状态栏、导航栏处理)

    Android应用中经常会有一些要求全屏显隐状态栏导航栏的需求.通过全屏沉浸式的处理可以让应用达到更好的显示效果.下面系统的讲解一下有关全屏,隐藏状态栏导航栏,沉浸式的知识. 在Android4.1之 ...

  7. android 特效相机实现,基于Android系统的相机特效软件的设计与实现

    摘要: 最近几年,随着科学技术的高速发展,智能手机或者智能平板等一些移动智能设备在各个年龄段的人群中已经有了非常高的普及率.这些智能设备与现代通信技术的紧密结合实现了音乐.图像.视频等多媒体信息与互联 ...

  8. android系统联系人可以转到blackberry os 7.,采取2步验证后如何同步iCloud联系人到黑莓OS10手机...

    我的备用机黑莓Q10之前一直使用iCloud同步联系人,直到约1个多月前,这个功能突然失效了,经常收到系统的通知,具体内容忘了,总之是不能再继续顺利进行联系人同步.虽然发生这个问题之后我几乎立刻就意识 ...

  9. Android 系统(153)--- M上默认接入点apn显示

    M上默认接入点apn显示 Android M上apn设置中界面显示及默认接入点配置 apn设置中的显示界面的apn是来源于apns-conf.xml中配置的对应运营商(根据mccmnc查询) 的所有a ...

最新文章

  1. 生化医学文章模式图素材
  2. ScrollView充满屏幕
  3. boost::describe模块宏BOOST_DESCRIBE_PP_POINTER的测试程序
  4. C#设计模式之十五迭代器模式(Iterator Pattern)【行为型】
  5. 我的世界服务器指令修改拔刀剑,我的世界拔刀剑Mod 合成刀方法作用
  6. 扫地机器人湿地_仅仅是打湿地板而已?定价高了,石头扫地机器人的拖地却仍很一般...
  7. baidu 地图 鼠标移上显示标签 鼠标离开隐藏标签
  8. 这个机器人不学数据集,“纯玩”get各类家务技能,LeCun觉得很赞
  9. svn+ssh 想要CheckOut不容易
  10. Visual Studio 单元测试之二---顺序单元测试
  11. 扒一扒微信后台架构.....
  12. Linux基本命令总结(初学者可以借鉴学习)
  13. paip.输入法编程---增加码表类型
  14. smobiler仿饿了么app筛选页面
  15. 计算机ps基础知识教案范文,ps基础教案
  16. 华为FPGA设计高级技巧xilinx篇阅读笔记一
  17. Angular4 - http
  18. 数据挖掘求职岗位要求分析
  19. 大学计算机打算及目的,计算机专业大学生毕业实习目的
  20. element ui滑动登录,密码强度提示

热门文章

  1. Cisco网络管理的35个常见问题及解答
  2. 华为机试HJ106:字符逆序
  3. html 画布 重置,html5清空画布的方法有哪些
  4. 论文计算机辅助辅助教学应用,计算机辅助教学应用论文.doc
  5. linux 设置时钟 教程,Linux hwclock命令参数及用法详解--Linux显示/设置硬件时钟命令...
  6. ubuntu下的常见12个命令---欢迎补充
  7. java td背景色_jQuery:无法更改表格单元格的背景颜色
  8. 听说你不会用datetime处理时间?
  9. 接口测试常用工具及测试方法(新手篇)
  10. html在线测试 css,HTML+CSS测试