HarmonyOS 項目實戰(zhàn)之通訊錄(Java)
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
1 簡介
通訊錄demo主要分為聯(lián)系人界面、設(shè)置緊急聯(lián)系人、服務(wù)卡片3個模塊,分為Java和JS兩個版本,本篇主要講解用盡可能的用Java去實現(xiàn)。
1.1 原型
感興趣的小伙伴,可以自己根據(jù)原型效果自己嘗試著去實現(xiàn)【通訊錄demo簡易原型】。



1.2 場景示例
通過學(xué)習(xí)與練習(xí)本demo,可以延伸至以下場景。



1.3 項目實戰(zhàn)
《HarmonyOS 項目實戰(zhàn)之通訊錄Demo(JS)》
《HarmonyOS 項目實戰(zhàn)之通訊錄(Java)》
《HarmonyOS 項目實戰(zhàn)之新聞頭條(ArkUI-TS》
2 功能開發(fā)
2.1 聯(lián)系人列表
2.1.1 實現(xiàn)效果
2.1.2 核心代碼
參考:ListContainer-常用組件開發(fā)指導(dǎo)-Java UI框架-UI-開發(fā)-HarmonyOS應(yīng)用開發(fā)
- ListContainer設(shè)置StickyContactProvider適配器
 - HeaderDecor頭部聯(lián)動效果設(shè)置
 - ContactData數(shù)據(jù)處理相關(guān)類,sortContactData方法用于排序等數(shù)據(jù)處理
 
- 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方法數(shù)據(jù)處理,排序,字母索引:
- 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 數(shù)據(jù)的增刪改查
2.2.1 實現(xiàn)效果

2.2.2 增刪改查實現(xiàn)
ListContainer刪除實現(xiàn)
- categoryData.deleteContactBeans(item);
 - categoryData.sortContactData();
 - mStickyContactProvider.setDataListChanged(categoryData.getResultList());
 
隨機添加一個聯(lián)系人
- categoryData.addContactBean("胡六一", "15269856587");
 - categoryData.sortContactData();
 - mStickyContactProvider.setDataListChanged(categoryData.getResultList());
 
ContactData數(shù)據(jù)處理效果類,實現(xiàn)數(shù)據(jù)增刪改查
- // 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 緊急聯(lián)系人數(shù)據(jù)存儲
輕量級數(shù)據(jù)存儲:輕量級數(shù)據(jù)存儲概述-輕量級數(shù)據(jù)存儲-數(shù)據(jù)管理-開發(fā)-HarmonyOS應(yīng)用開發(fā)
Key-Value數(shù)據(jù)結(jié)構(gòu)
一種鍵值結(jié)構(gòu)數(shù)據(jù)類型。Key是不重復(fù)的關(guān)鍵字,Value是數(shù)據(jù)值。
運作機制
- 本模塊提供輕量級數(shù)據(jù)存儲的操作類,應(yīng)用通過這些操作類完成數(shù)據(jù)庫操作。
 - 借助DatabaseHelper API,應(yīng)用可以將指定文件的內(nèi)容加載到Preferences實例,每個文件最多有一個Preferences實例,系統(tǒng)會通過靜態(tài)容器將該實例存儲在內(nèi)存中,直到應(yīng)用主動從內(nèi)存中移除該實例或者刪除該文件。
 - 獲取到文件對應(yīng)的Preferences實例后,應(yīng)用可以借助Preferences API,從Preferences實例中讀取數(shù)據(jù)或者將數(shù)據(jù)寫入Preferences實例,通過flush或者flushSync將Preferences實例持久化。
 
核心代碼實現(xiàn)
添加緊急聯(lián)系人,并通知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封裝工具類,實現(xiàn)數(shù)據(jù)存儲。
- 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,否則存入的數(shù)據(jù)無法獲取
 - 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 第三方跳轉(zhuǎn)
2.3.1 實現(xiàn)效果

2.3.2 撥打電話與發(fā)送短信
- /**
 - * 跳轉(zhuǎn)系統(tǒng)短信
 - */
 - 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)) // 設(shè)置號碼
 - .withFlags(Intent.FLAG_NOT_OHOS_COMPONENT)
 - .build();
 - intent.setOperation(operation);
 - context.startAbility(intent, 11);
 - }
 - /**
 - * 申請撥打電話權(quán)限
 - */
 - private boolean requestPermissions() {
 - if (context.verifySelfPermission("android.permission.CALL_PHONE") != IBundleManager.PERMISSION_GRANTED) {
 - // 應(yīng)用未被授予權(quán)限
 - if (context.canRequestPermission("android.permission.CALL_PHONE")) {
 - // 是否可以申請彈框授權(quán)(首次申請或者用戶未選擇禁止且不再提示)
 - context.requestPermissionsFromUser(new String[]{"android.permission.CALL_PHONE"}, 100);
 - }
 - return false;
 - } else {
 - // 權(quán)限已被授予
 - return true;
 - }
 - }
 - /**
 - * 直接撥打電話
 - * 需要申請權(quán)限
 - */
 - private void doCall(String destinationNum) {
 - if (!requestPermissions()) {
 - return;
 - }
 - Intent intent = new Intent();
 - Operation operation = new Intent.OperationBuilder()
 - .withAction("android.intent.action.CALL") // 系統(tǒng)應(yīng)用撥號盤
 - .withUri(Uri.parse("tel:" + destinationNum)) // 設(shè)置號碼
 - .withFlags(2)
 - .build();
 - intent.setOperation(operation);
 - // 啟動Ability
 - context.startAbility(intent, 10);
 - }
 - /**
 - * 跳轉(zhuǎn)系統(tǒng)撥打電話界面
 - */
 - private void doDial(String destinationNum) {
 - Intent intent = new Intent();
 - Operation operation = new Intent.OperationBuilder()
 - .withAction(IntentConstants.ACTION_DIAL) // 系統(tǒng)應(yīng)用撥號盤
 - // .withBundleName(context.getCallingBundle()) // 應(yīng)用撥號選擇器
 - .withUri(Uri.parse("tel:" + destinationNum)) // 設(shè)置號碼
 - .withFlags(2)
 - .build();
 - intent.setOperation(operation);
 - // 啟動Ability
 - context.startAbility(intent, 10);
 - }
 
2.4 JS服務(wù)卡片
2.4.1 實現(xiàn)效果

2.4.2 創(chuàng)建卡片模板
使用DevEco Studio創(chuàng)建卡片工程
創(chuàng)建成功后,在config.json的module中會生成js模塊,用于對應(yīng)卡片的js相關(guān)資源,配置示例如下:
- "js": [
 - {
 - "pages": [
 - "pages/index/index"
 - ],
 - "name": "widget",
 - "window": {
 - "designWidth": 720,
 - "autoDesignWidth": true
 - },
 - "type": "form"
 - }
 - ]
 
config.json文件“abilities”配置forms模塊細節(jié)如下:
- "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
 - }
 - ]
 
創(chuàng)建一個ContactPersonAbility,覆寫卡片相關(guān)回調(diào)函數(shù)。
- onCreateForm(Intent intent)
 - onUpdateForm(long formId)
 - onDeleteForm(long formId)
 - onCastTempForm(long formId)
 - onEventNotify(Map
 - onTriggerFormEvent(long formId, String message)
 - onAcquireFormState(Intent intent)
 
當卡片使用方請求獲取卡片時,卡片提供方會被拉起并調(diào)用onCreateForm(Intent intent)回調(diào),intent中會帶有卡片ID、卡片名稱和卡片外觀規(guī)格信息,可按需獲取使用。
開發(fā)JS卡片時,F(xiàn)ormAbility可以繼承AceAbility或Ability,繼承Ability時,需在onStart()方法中額外設(shè)置路由信息。
2.4.3 卡片數(shù)據(jù)綁定
- @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 卡片數(shù)據(jù)更新
- 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"
 - }
 - }
 - }
 
卡片支持觸發(fā)事件,覆寫onTriggerFormEvent方法實現(xiàn)對事件的觸發(fā),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還有很多需要完善的地方
- 滑動時,上滑頭部聯(lián)動效果,索引有時會錯亂;
 - 搜索功能未實現(xiàn);
 - 緊急聯(lián)系人沒有和列表數(shù)據(jù)聯(lián)動。
 
4 總結(jié)
代碼地址: https://gitee.com/hu-lingqing/contact-person.git


















 
 
 


 
 
 
 