HarmonyOS 項目實戰之通訊錄(Java)
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);
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()));
- }
- }
2.2 數據的增刪改查
2.2.1 實現效果

2.2.2 增刪改查實現
ListContainer刪除實現
- categoryData.deleteContactBeans(item);
- categoryData.sortContactData();
- mStickyContactProvider.setDataListChanged(categoryData.getResultList());
隨機添加一個聯系人
- categoryData.addContactBean("胡六一", "15269856587");
- categoryData.sortContactData();
- mStickyContactProvider.setDataListChanged(categoryData.getResultList());
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不會滑動
- }
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);
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);
- }
- }
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);
- }
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"
- }
- ]
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
- }
- ]
創建一個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;
- }
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();
- }
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"
- }
- }
- }
卡片支持觸發事件,覆寫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;
- }
- }
3 注意事項
Demo還有很多需要完善的地方
- 滑動時,上滑頭部聯動效果,索引有時會錯亂;
- 搜索功能未實現;
- 緊急聯系人沒有和列表數據聯動。
4 總結
代碼地址: https://gitee.com/hu-lingqing/contact-person.git