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_timesort_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。也就是说,一个联系人的所有账号共享同一个星标状态。你想想看,这其实挺合理的——收藏是针对联系人本身,而不是针对某个账号。

收藏夹快捷访问:让用户少点两下

功能做完了,但怎么让用户快速访问?我见过很多应用,把收藏夹藏在三级菜单里,用户找都找不到。这其实违背了收藏夹的初衷。

我个人建议,至少提供三种快捷访问方式:

  1. 主界面顶部快捷入口:比如一个星标图标,点击直接进入收藏列表。
  2. 侧边栏或抽屉菜单:放在显眼位置,比如「我的收藏」。
  3. 桌面小部件:这个比较进阶,但用户反馈非常好。直接在主屏幕显示收藏联系人,点击就拨号。

来看一个简单的快捷访问实现——用 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;
        });
    }
}
核心思路: 快捷访问的关键是「减少操作步骤」。用户收藏一个联系人,就是为了下次能快速找到。所以,收藏夹的入口一定要浅,操作一定要快。

知识体系图:收藏夹核心逻辑

下面这张图,是我自己总结的收藏夹功能核心逻辑。你可以看到,从数据层到展示层,每一步都有对应的设计考量。

联系人收藏夹核心逻辑 数据层 业务层 展示层 数据库设计 • STARRED 字段 • 单独收藏表 • 排序/时间字段 业务逻辑 • 添加/取消收藏 • 批量操作 • 同步策略 UI 交互 • 快捷入口 • 星标图标 • 桌面小部件 设计要点总结 1. 数据层:STARRED 字段 + 独立收藏表,兼顾简单与灵活 2. 业务层:操作前检查联系人是否存在,避免静默失败 3. 展示层:入口要浅,操作要快,减少用户点击次数

避坑指南:我踩过的几个坑

做收藏夹功能,有几个坑我印象特别深,分享给你:

  • 同步问题:我曾经做过一个项目,用户在一台设备上收藏了联系人,但另一台设备上没同步。后来发现是 ContentObserver 没注册好。记住,收藏状态变化后,要主动通知 UI 刷新。
  • 性能问题:如果你用 STARRED 字段直接查询,记得加索引。否则数据量大了,查询会变慢。我遇到过用户有 5000 个联系人,查询收藏列表花了 3 秒,直接被投诉。
  • UI 反馈:用户点击星标图标时,一定要有即时反馈。比如图标变色、震动、或者 Toast 提示。我见过有些应用点击后没反应,用户以为没点上,又点了一次,结果取消了收藏。
一个小技巧: 在列表里展示星标图标时,可以用 AnimationDrawable 做一个简单的缩放动画。用户点击时,图标先缩小再恢复,视觉反馈非常舒服。代码很简单,但用户体验提升很明显。

好了,关于收藏夹的设计和实现,今天就聊到这里。记住,收藏夹的核心不是技术有多复杂,而是让用户「少点一下」。你想想看,用户愿意花时间收藏一个联系人,就是希望下次能更快找到他。别辜负了这份信任。


公众号:蓝海资料掘金营,微信deep3321