想了解更多内容,请访问:
51CTO和华为官方合作共建的鸿蒙技术社区
https://harmonyos.51cto.com
1 简介
通讯录demo主要分为联系人界面、设置紧急联系人、服务卡片3个模块,分为Java和JS两个版本,本篇主要讲解用尽可能的用Java去实现。
1.1 原型
感兴趣的小伙伴,可以自己根据原型效果自己尝试着去实现【通讯录demo简易原型】。



1.2 场景示例
通过学习与练习本demo,可以延伸至以下场景。



1.3 项目实战
《HarmonyOS 项目实战之通讯录Demo(JS)》
《HarmonyOS 项目实战之通讯录(Java)》
《HarmonyOS 项目实战之新闻头条(ArkUI-TS》
2 功能开发
2.1 联系人列表
2.1.1 实现效果
2.1.2 核心代码
参考:ListContainer-常用组件开发指导-Java UI框架-UI-开发-HarmonyOS应用开发
- ListContainer设置StickyContactProvider适配器
- HeaderDecor头部联动效果设置
- ContactData数据处理相关类,sortContactData方法用于排序等数据处理
ContactData categoryData = ContactData.get();
categoryData.sortContactData();
contactList = (ListContainer) findComponentById(ResourceTable.Id_contactList);
Text headerText = (Text) findComponentById(ResourceTable.Id_sticky_text);
List<ContactBean> dataList = categoryData.getResultList();
mStickyContactProvider = new StickyContactProvider(this, dataList);
contactList.setItemProvider(mStickyContactProvider);
HeaderDecor headerDecor = new HeaderDecor(contactList, headerText);
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
sortContactData方法数据处理,排序,字母索引:
public void sortContactData() {
List<ContactBean> mContactList = new ArrayList<>();
Map<String, String> map = new HashMap<>();
for (ContactBean contactBean : mContactBeans) {
String pinyin = Utils.getPingYin(contactBean.getName());
map.put(pinyin, contactBean.getName());
contactBean.setNamepy(pinyin);
mContactList.add(contactBean);
}
mContactList.sort(new ContactComparator());
characterList = new ArrayList<>();
resultList = new ArrayList<>();
for (ContactBean contactBean : mContactList) {
String namepy = contactBean.getNamepy();
String character = (namepy.charAt(0) + "").toUpperCase(Locale.ENGLISH);
if (!characterList.contains(character)) {
if (character.hashCode() >= "A".hashCode() && character.hashCode() <= "Z".hashCode()) { // 是字母
characterList.add(character);
resultList.add(new ContactBean(character, ContactBean.ITEM_TYPE.ITEM_TYPE_CHARACTER.ordinal()));
} else {
if (!characterList.contains("#")) {
characterList.add("#");
resultList.add(new ContactBean("#", ContactBean.ITEM_TYPE.ITEM_TYPE_CHARACTER.ordinal()));
}
}
}
resultList.add(new ContactBean(contactBean.getName(), contactBean.getTelephone(), map.get(namepy), ContactBean.ITEM_TYPE.ITEM_TYPE_CONTACT.ordinal()));
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
2.2 数据的增删改查
2.2.1 实现效果

2.2.2 增删改查实现
ListContainer删除实现
categoryData.deleteContactBeans(item);
categoryData.sortContactData();
mStickyContactProvider.setDataListChanged(categoryData.getResultList());
- 1.
- 2.
- 3.
随机添加一个联系人
categoryData.addContactBean("胡六一", "15269856587");
categoryData.sortContactData();
mStickyContactProvider.setDataListChanged(categoryData.getResultList());
- 1.
- 2.
- 3.
ContactData数据处理效果类,实现数据增删改查
// Generate the javaBean of ContactData
public static ContactData get() {
return new ContactData();
}
public List<ContactBean> getDefaultContactBeans() {
return mDefaultContactBeans;
}
public List<ContactBean> getContactBeans() {
return mContactBeans;
}
public void addContactBean(String name, String phone) {
mContactBeans.add(new ContactBean(name, phone));
}
public List<ContactBean> deleteContactBeans(ContactBean item) {
mContactBeans.removeIf(contactBean -> contactBean.getName().equals(item.getName()));
return mContactBeans;
}
public List<ContactBean> getResultList() {
return resultList;
}
public List<String> getCharacterList() {
return characterList;
}
public int getScrollPosition(String character) {
if (characterList.contains(character)) {
for (int i = 0; i < resultList.size(); i++) {
if (resultList.get(i).getCharacter().equals(character)) {
return i;
}
}
}
return -1; // -1不会滑动
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
2.2.3 紧急联系人数据存储
轻量级数据存储:轻量级数据存储概述-轻量级数据存储-数据管理-开发-HarmonyOS应用开发
Key-Value数据结构
一种键值结构数据类型。Key是不重复的关键字,Value是数据值。
运作机制
- 本模块提供轻量级数据存储的操作类,应用通过这些操作类完成数据库操作。
- 借助DatabaseHelper API,应用可以将指定文件的内容加载到Preferences实例,每个文件最多有一个Preferences实例,系统会通过静态容器将该实例存储在内存中,直到应用主动从内存中移除该实例或者删除该文件。
- 获取到文件对应的Preferences实例后,应用可以借助Preferences API,从Preferences实例中读取数据或者将数据写入Preferences实例,通过flush或者flushSync将Preferences实例持久化。
核心代码实现
添加紧急联系人,并通知java卡片更新。
ZSONObject zsonObject = new ZSONObject();
zsonObject.put("urgent1", nameTf1.getText());
zsonObject.put("urgentPhone1", phoneTf1.getText());
zsonObject.put("urgent2", nameTf2.getText());
zsonObject.put("urgentPhone2", phoneTf2.getText());
PreferenceUtils.putString(getContext(),"urgentPerson", ZSONObject.toZSONString(zsonObject));
FormBindingData formBindingData = new FormBindingData(zsonObject);
((ContactPersonAbility) getAbility()).confirmUpdateForm(formBindingData);
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
PreferenceUtils封装工具类,实现数据存储。
public class PreferenceUtils {
private static String PREFERENCE_FILE_NAME = "prefrence_file";
private static Preferences preferences;
private static DatabaseHelper databaseHelper;
private static Preferences.PreferencesObserver mPreferencesObserver;
private static void initPreference(Context context) {
if (databaseHelper == null) {
databaseHelper = new DatabaseHelper(context);
}
if (preferences == null) {
preferences = databaseHelper.getPreferences(PREFERENCE_FILE_NAME);
}
}
//存放、获取时传入的context必须是同一个context,否则存入的数据无法获取
public static void putString(Context context, String key, String value) {
initPreference(context);
preferences.putString(key, value);
preferences.flush();
}
/**
* @param context 上下文
* @param key 键
* @return 获取的String 默认值为:null
*/
public static String getString(Context context, String key) {
initPreference(context);
return preferences.getString(key, null);
}
public static String getString(Context context, String key, String d) {
initPreference(context);
return preferences.getString(key, d);
}
public static void putInt(Context context, String key, int value) {
initPreference(context);
preferences.putInt(key, value);
preferences.flush();
}
/**
* @param context 上下文
* @param key 键
* @return 获取int的默认值为:-1
*/
public static int getInt(Context context, String key) {
initPreference(context);
return preferences.getInt(key, -1);
}
public static void putLong(Context context, String key, long value) {
initPreference(context);
preferences.putLong(key, value);
preferences.flush();
}
/**
* @param context 上下文
* @param key 键
* @return 获取long的默认值为:-1
*/
public static long getLong(Context context, String key) {
initPreference(context);
return preferences.getLong(key, -1L);
}
public static void putBoolean(Context context, String key, boolean value) {
initPreference(context);
preferences.putBoolean(key, value);
preferences.flush();
}
/**
* @param context 上下文
* @param key 键
* @return 获取boolean的默认值为:false
*/
public static boolean getBoolean(Context context, String key) {
initPreference(context);
return preferences.getBoolean(key, false);
}
public static void putFloat(Context context, String key, float value) {
initPreference(context);
preferences.putFloat(key, value);
preferences.flush();
}
/**
* @param context 上下文
* @param key 键
* @return 获取float的默认值为:0.0
*/
public static float getFloat(Context context, String key) {
initPreference(context);
return preferences.getFloat(key, 0.0F);
}
public static void putStringSet(Context context, String key, Set<String> set) {
initPreference(context);
preferences.putStringSet(key, set);
preferences.flush();
}
/**
* @param context 上下文
* @param key 键
* @return 获取set集合的默认值为:null
*/
public static Set<String> getStringSet(Context context, String key) {
initPreference(context);
return preferences.getStringSet(key, null);
}
public static boolean deletePreferences(Context context) {
initPreference(context);
boolean isDelete = databaseHelper.deletePreferences(PREFERENCE_FILE_NAME);
return isDelete;
}
public static void registerObserver(Context context, Preferences.PreferencesObserver preferencesObserver) {
initPreference(context);
mPreferencesObserver = preferencesObserver;
preferences.registerObserver(mPreferencesObserver);
}
public static void unregisterObserver() {
if (mPreferencesObserver != null) {
// 向preferences实例注销观察者
preferences.unregisterObserver(mPreferencesObserver);
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
2.3 第三方跳转
2.3.1 实现效果

2.3.2 拨打电话与发送短信
/**
* 跳转系统短信
*/
private void doMessage(String telephone) {
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
// .withAction("android.intent.action.SENDTO") // Android写法 android.intent.action.SENDTO
.withAction(IntentConstants.ACTION_SEND_SMS)
.withUri(Uri.parse("smsto:" + telephone)) // 设置号码
.withFlags(Intent.FLAG_NOT_OHOS_COMPONENT)
.build();
intent.setOperation(operation);
context.startAbility(intent, 11);
}
/**
* 申请拨打电话权限
*/
private boolean requestPermissions() {
if (context.verifySelfPermission("android.permission.CALL_PHONE") != IBundleManager.PERMISSION_GRANTED) {
// 应用未被授予权限
if (context.canRequestPermission("android.permission.CALL_PHONE")) {
// 是否可以申请弹框授权(首次申请或者用户未选择禁止且不再提示)
context.requestPermissionsFromUser(new String[]{"android.permission.CALL_PHONE"}, 100);
}
return false;
} else {
// 权限已被授予
return true;
}
}
/**
* 直接拨打电话
* 需要申请权限
*/
private void doCall(String destinationNum) {
if (!requestPermissions()) {
return;
}
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withAction("android.intent.action.CALL") // 系统应用拨号盘
.withUri(Uri.parse("tel:" + destinationNum)) // 设置号码
.withFlags(2)
.build();
intent.setOperation(operation);
// 启动Ability
context.startAbility(intent, 10);
}
/**
* 跳转系统拨打电话界面
*/
private void doDial(String destinationNum) {
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withAction(IntentConstants.ACTION_DIAL) // 系统应用拨号盘
// .withBundleName(context.getCallingBundle()) // 应用拨号选择器
.withUri(Uri.parse("tel:" + destinationNum)) // 设置号码
.withFlags(2)
.build();
intent.setOperation(operation);
// 启动Ability
context.startAbility(intent, 10);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
2.4 JS服务卡片
2.4.1 实现效果

2.4.2 创建卡片模板
使用DevEco Studio创建卡片工程
创建成功后,在config.json的module中会生成js模块,用于对应卡片的js相关资源,配置示例如下:
"js": [
{
"pages": [
"pages/index/index"
],
"name": "widget",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"type": "form"
}
]
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
config.json文件“abilities”配置forms模块细节如下:
"name": "com.huhu.contact.ContactPersonAbility",
"icon": "$media:icon",
"description": "$string:contactpersonability_description",
"formsEnabled": true,
"label": "$string:contact_ContactPersonAbility",
"type": "page",
"forms": [
{
"jsComponentName": "widget",
"isDefault": true,
"scheduledUpdateTime": "10:30",
"defaultDimension": "2*2",
"name": "widget",
"description": "This is a service widget",
"colorMode": "auto",
"type": "JS",
"supportDimensions": [
"2*2"
],
"updateEnabled": true,
"updateDuration": 1
}
]
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
创建一个ContactPersonAbility,覆写卡片相关回调函数。
- onCreateForm(Intent intent)
- onUpdateForm(long formId)
- onDeleteForm(long formId)
- onCastTempForm(long formId)
- onEventNotify(Map
- onTriggerFormEvent(long formId, String message)
- onAcquireFormState(Intent intent)
当卡片使用方请求获取卡片时,卡片提供方会被拉起并调用onCreateForm(Intent intent)回调,intent中会带有卡片ID、卡片名称和卡片外观规格信息,可按需获取使用。
开发JS卡片时,FormAbility可以继承AceAbility或Ability,继承Ability时,需在onStart()方法中额外设置路由信息。
2.4.3 卡片数据绑定
@Override
public ProviderFormInfo bindFormData() {
HiLog.info(TAG, "bind form data");
ProviderFormInfo providerFormInfo = new ProviderFormInfo();
String urgentPersonStr = PreferenceUtils.getString(context, "urgentPerson", "");
ZSONObject zsonObject = ZSONObject.stringToZSON(urgentPersonStr);
if (dimension == DEFAULT_DIMENSION_2X2) {
if (zsonObject != null) {
providerFormInfo.setJsBindingData(new FormBindingData(zsonObject));
}
}
return providerFormInfo;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
2.4.4 卡片数据更新
public void confirmUpdateForm(FormBindingData formBindingData) {
FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
List<Long> allFormIdFromSharePreference = formControllerManager.getAllFormIdFromSharePreference();
if (allFormIdFromSharePreference == null || allFormIdFromSharePreference.isEmpty()) return;
Long formId = allFormIdFromSharePreference.get(0);
try {
updateForm(formId,formBindingData);
} catch (FormException e) {
e.printStackTrace();
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
2.4.5 卡片事件处理
{
"data": {
"text_content": "Name",
"cardPrimaryText": "Contacts",
"cardSecondaryText": "+8612345678912",
"urgent1": "无",
"urgent2": "无",
"urgentPhone1": "+8612345678912",
"urgentPhone2": "+8612345678915"
},
"actions": {
"urgentCall1": {
"action": "message",
"params": {
"action": "urgentCall1",
"phoneNumber": "10086"
}
},
"urgentCall2": {
"action": "message",
"params": {
"action": "urgentCall2",
"phoneNumber": "15565339857"
}
},
"startMainRouter": {
"action": "router",
"abilityName": "com.huhu.contact.ContactPersonAbility"
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
卡片支持触发事件,覆写onTriggerFormEvent方法实现对事件的触发,doCall就是前面的播打电话的方法
@Override
protected void onTriggerFormEvent(long formId, String message) {
super.onTriggerFormEvent(formId, message);
HiLog.info(loglabel, "onTriggerFormEvent: " + message);
FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
FormController formController = formControllerManager.getController(formId);
formController.onTriggerFormEvent(formId, message);
ZSONObject params = ZSONObject.stringToZSON(message);
String action = params.getString("action");
String phoneNumber = params.getString("phoneNumber");
HiLog.info(loglabel, "onTriggerFormEvent: action:" + action);
String urgentPersonStr = PreferenceUtils.getString(this, "urgentPerson", "");
ZSONObject zsonObject = ZSONObject.stringToZSON(urgentPersonStr);
switch (action) {
case "urgentCall1":
String urgentPhone1 = zsonObject.getString("urgentPhone1");
doCall(urgentPhone1);
break;
case "urgentCall2":
String urgentPhone2 = zsonObject.getString("urgentPhone2");
doCall(urgentPhone2);
break;
default:
break;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
3 注意事项
Demo还有很多需要完善的地方
- 滑动时,上滑头部联动效果,索引有时会错乱;
- 搜索功能未实现;
- 紧急联系人没有和列表数据联动。
4 总结
代码地址: https://gitee.com/hu-lingqing/contact-person.git
想了解更多内容,请访问:
51CTO和华为官方合作共建的鸿蒙技术社区
https://harmonyos.51cto.com