25、联系人收藏夹:收藏夹表设计、星标联系人、收藏夹快捷访问
说实话,收藏夹这个功能,在联系人系统里看着不起眼,但用起来是真香。我自己做项目的时候,发现用户最常抱怨的就是「找个人翻半天」。你想想看,几百个联系人,每次都要从头搜,谁受得了?
所以,星标收藏这个机制,说白了就是给联系人加个「快捷通道」。今天我们就来聊聊,怎么在 Android 里把这件事做好。
收藏夹表设计:不只是加个字段
很多人一上来就想,不就是加个布尔字段吗?is_favorite 搞定。嗯,我刚开始也这么干过。但后来发现,事情没那么简单。
先看官方是怎么做的。Android 的 ContactsContract.Contacts 表里,有一个字段叫 STARRED。它是一个整型,0 表示未收藏,1 表示已收藏。就这么简单?对,但背后有讲究。
我个人习惯,在设计本地数据库时,会单独建一张收藏夹表。为什么?因为如果你直接在联系人表上加字段,每次查询收藏列表都要扫全表。数据量大了,性能就下来了。
来看我常用的表结构:
CREATE TABLE favorites (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
contact_id INTEGER NOT NULL UNIQUE,
starred INTEGER DEFAULT 0,
starred_time INTEGER DEFAULT 0,
sort_order INTEGER DEFAULT 0,
FOREIGN KEY (contact_id) REFERENCES contacts(_id)
);
这里我加了两个额外的字段:starred_time 和 sort_order。为什么?
- starred_time:记录收藏时间。用户可能想按「最近收藏」排序。
- sort_order:手动排序。有些用户喜欢自己拖拽排序,这个字段就是干这个用的。
STARRED 字段就够了。但如果你要做排序、分组、或者未来扩展,单独建表会更灵活。
星标联系人:STARRED 字段的正确用法
在 Android 的 ContactsContract 里,操作星标其实很简单。核心就是读写 Contacts.STARRED 字段。
设置星标:
ContentValues values = new ContentValues();
values.put(Contacts.STARRED, 1); // 1 表示收藏
Uri uri = Uri.withAppendedPath(Contacts.CONTENT_URI, String.valueOf(contactId));
getContentResolver().update(uri, values, null, null);
取消星标:
ContentValues values = new ContentValues();
values.put(Contacts.STARRED, 0);
Uri uri = Uri.withAppendedPath(Contacts.CONTENT_URI, String.valueOf(contactId));
getContentResolver().update(uri, values, null, null);
查询所有星标联系人:
Cursor cursor = getContentResolver().query(
Contacts.CONTENT_URI,
null,
Contacts.STARRED + " = ?",
new String[]{"1"},
Contacts.DISPLAY_NAME + " ASC"
);
Contacts.STARRED 字段时,没有检查联系人是否存在。如果联系人被删除了,更新操作会静默失败,不会抛异常。所以,操作前最好先查一下联系人是否存在。
这里有个细节:STARRED 字段是存储在 Contacts 表里的,不是 RawContacts。也就是说,一个联系人的所有账号共享同一个星标状态。你想想看,这其实挺合理的——收藏是针对联系人本身,而不是针对某个账号。
收藏夹快捷访问:让用户少点两下
功能做完了,但怎么让用户快速访问?我见过很多应用,把收藏夹藏在三级菜单里,用户找都找不到。这其实违背了收藏夹的初衷。
我个人建议,至少提供三种快捷访问方式:
- 主界面顶部快捷入口:比如一个星标图标,点击直接进入收藏列表。
- 侧边栏或抽屉菜单:放在显眼位置,比如「我的收藏」。
- 桌面小部件:这个比较进阶,但用户反馈非常好。直接在主屏幕显示收藏联系人,点击就拨号。
来看一个简单的快捷访问实现——用 RecyclerView 展示收藏联系人,并支持快速拨号:
public class FavoriteAdapter extends RecyclerView.Adapter<FavoriteAdapter.ViewHolder> {
private List<Contact> favorites;
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Contact contact = favorites.get(position);
holder.name.setText(contact.getDisplayName());
holder.phone.setText(contact.getPhoneNumber());
// 点击直接拨号
holder.itemView.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:" + contact.getPhoneNumber()));
v.getContext().startActivity(intent);
});
// 长按取消收藏
holder.itemView.setOnLongClickListener(v -> {
removeFavorite(contact);
return true;
});
}
}
知识体系图:收藏夹核心逻辑
下面这张图,是我自己总结的收藏夹功能核心逻辑。你可以看到,从数据层到展示层,每一步都有对应的设计考量。
避坑指南:我踩过的几个坑
做收藏夹功能,有几个坑我印象特别深,分享给你:
- 同步问题:我曾经做过一个项目,用户在一台设备上收藏了联系人,但另一台设备上没同步。后来发现是 ContentObserver 没注册好。记住,收藏状态变化后,要主动通知 UI 刷新。
- 性能问题:如果你用
STARRED字段直接查询,记得加索引。否则数据量大了,查询会变慢。我遇到过用户有 5000 个联系人,查询收藏列表花了 3 秒,直接被投诉。 - UI 反馈:用户点击星标图标时,一定要有即时反馈。比如图标变色、震动、或者 Toast 提示。我见过有些应用点击后没反应,用户以为没点上,又点了一次,结果取消了收藏。
AnimationDrawable 做一个简单的缩放动画。用户点击时,图标先缩小再恢复,视觉反馈非常舒服。代码很简单,但用户体验提升很明显。
好了,关于收藏夹的设计和实现,今天就聊到这里。记住,收藏夹的核心不是技术有多复杂,而是让用户「少点一下」。你想想看,用户愿意花时间收藏一个联系人,就是希望下次能更快找到他。别辜负了这份信任。
公众号:蓝海资料掘金营,微信deep3321