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作为程序的主布局文件,在里面加入如下代码:
- <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>
布局文件很简单,里面放入了一个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" >
- <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>
在这个布局文件中,首先是放入了一个和前面完成一样的分组布局,因为不仅界面头部需要展示分组,在每个分组内的第一个无素之前都需要展示分组布局。然后是加入一个简单的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;
- }
- @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;
- }
- /**
- * 给当前适配器传入一个分组工具。
- *
- * @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;
- @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();
- }
- }
- /**
- * 为联系人ListView设置监听事件,根据当前的滑动状态来改变分组的显示位置,从而实现挤压动画的效果。
- */
- 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;
- }
- });
- }
- /**
- * 获取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-sdk
- android:minSdkVersion="8"
- android:targetSdkVersion="8" />
- <uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
- <application
- android:allowBackup="true"
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name"
- android:theme="@android:style/Theme.NoTitleBar"
- >
- <activity
- android: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系统联系人全特效实现(上),分组导航和挤压动画的更多相关文章
- Android系统联系人全特效实现(下),字母表快速滚动
在上一篇文章中,我和大家一起实现了类似于Android系统联系人的分组导航和挤压动画功能,不过既然文章名叫做<Android系统联系人全特效实现>,那么没有快速滚动功能显然是称不上&quo ...
- android系统联系人分组特效实现(1)---分组导航和挤压动画
1.打开activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/andr ...
- android系统联系人分组特效实现(2)---字母表快速滚动
要实现这种功能,只需要在 android系统联系人分组特效实现(1)---分组导航和挤压动画 的基础上再加上一个自定义控件即可完成. 1.新建项目,继续新建一个java类,BladeView,用 ...
- 【Android 系统开发】 编译 Android文件系统 u-boot 内核 并烧写到 OK-6410A 开发板上
博客地址 : http://blog.csdn.net/shulianghan/article/details/40299813 本篇文章中用到的工具源码下载 : -- ok-6410A 附带的 A ...
- Android系统源代码学习步骤
目前,互联网行业正在朝着移动互联网方向强劲地发展,而移动互联网的发展离不开背后的移动平台的支撑.众所周知,如今在移动平台市场上,苹果的iOS.谷歌的Android和微软的Windows Phone系统 ...
- 线程、进程概念与Android系统组件的关系
Android系统是Google公司基于Linux内核开发的开源手机操作系统.通过利用 Linux 内核的优势,Android 系统使用了大量操作系统服务,包括进程管理.内存管理.网络堆栈.驱动程序. ...
- Android系统init进程启动及init.rc全解析
转:https://blog.csdn.net/zhonglunshun/article/details/78615980 服务启动机制system/core/init/init.c文件main函数中 ...
- Android(java)学习笔记250:ContentProvider使用之获得系统联系人信息02(掌握)
1.重要: 系统删除一个联系人,默认情况下并不是把这个联系人直接删除掉了,只是做了一个标记,标记为被删除. 2.前面一讲说过了如何获取系统联系人信息(通过ContentProvider),获取联系人信 ...
- Android(java)学习笔记249:ContentProvider使用之获得系统联系人信息01
1.系统联系人的数据库(3张最重要的表) (1)raw_contacts 联系人表 保存联系人的id contact_id (2)data 数据表 保存联系人的数据 ( ...
随机推荐
- 请求筛选模块被配置为拒绝包含 hiddenSegment 节的 URL 中的路径
转自原文 请求筛选模块被配置为拒绝包含 hiddenSegment 节的 URL 中的路径. 打开C:\Windows\System32\inetsrv\config路径 找到applicationH ...
- [Node] Run Any Version of a Node Tool with npx
As node projects evolve, new features are added all the time. This results in different errors or re ...
- UIActionSheet上加入UIPickerView iOS8替换方案
此套替换方案採用"UIView+动画"方式实现(将UIActionSheet替换为UIView) 界面层级例如以下: 第一层:view(这一层充满整个屏幕,初始化时颜色为透明.us ...
- BestCoder Round #11 (Div. 2) 前三题题解
题目链接: huangjing hdu5054 Alice and Bob 思路: 就是(x,y)在两个參考系中的表示演全然一样.那么仅仅可能在这个矩形的中点.. 题目: Alice and Bob ...
- android闹钟实现原理
闹钟的原理可用下面我自己画的一幅图来概括:(不对的地方,尽管吐槽) 我们来看看新建闹钟到闹钟响铃的步骤: 1.新建一个闹钟: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...
- Yii Framework2.0开发教程(1)配置环境及第一个应用HelloWorld
准备工作: 我用的开发环境是windows下的apache+mysql+php 编辑器不知道该用哪个好.临时用dreamweaver吧 我自己的http://localhost/相应的根文件夹是E:/ ...
- thinkphp3.2 图片平均颜色值
public function imgColor($imgUrl) { $imageInfo = getimagesize($imgUrl); //图片类型 $imgType = strtolower ...
- 详解HTML的a标签(超链接标签)
原文 简书原文:https://www.jianshu.com/p/d6a2499db73b 大纲 1.什么是<a>标签 2.<a>标签的几个重要属性 3.a标签的运行机制 4 ...
- centos7安装nginx的两种方法
第一种方式:通过yum安装 直接通过 yum install nginx 肯定是不行的,因为yum没有nginx,所以首先把 nginx 的源加入 yum 中 运行下面的命令: 1.将nginx放到y ...
- sublime-1 sublime设置到鼠标右键
sublime-1 sublime设置到鼠标右键 一.总结 一句话总结:其实windows可以更加熟悉一点才好,毕竟用的那么多,regedit可以添加注册,也可以添加鼠标右键选项. 二.sublime ...