我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android系统联系人全特效实现(上)分组导航和挤压动画(附源码)

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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:class="lazy" data-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> {

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

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;

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

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 - t itleHeight;
params.topMargin = (int) pushedDistance;
titleLayout.setLayoutParams(params);
} else {
if (params.topMargin != 0) {
params.topMargin = 0;
titleLayout.setLayoutParams(params);
}
}
}
}
lastFirstVisibleItem = firstVisibleItem;
}
});
}

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获取手机通讯录、sim卡联系人及调用拨号界面方法使用adb命令向Android模拟器中导入通讯录联系人的方法Android之联系人PinnedHeaderListView使用介绍Android根据电话号码获得联系人头像实例代码Android仿微信联系人按字母排序android 加载本地联系人实现方法Android保存联系人到通讯录的方法


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android系统联系人全特效实现(上)分组导航和挤压动画(附源码)

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android系统联系人全特效实现(上)分组导航和挤压动画(附源码)

记得在我刚接触Android的时候对系统联系人中的特效很感兴趣,它会根据手机中联系人姓氏的首字母进行分组,并在界面的最顶端始终显示一个当前的分组。如下图所示: 最让我感兴趣的是,当后一个分组和前一个分组相碰时,会产生一个上顶的挤压动画。那
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录