深圳幻海软件技术有限公司 欢迎您!

HarmonyOS 项目实战之通讯录(Java)

2023-02-28

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

想了解更多内容,请访问:

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(keynull); 
    } 
 
    public static String getString(Context context, String key, String d) { 
        initPreference(context); 
        return preferences.getString(key, d); 
    } 
 
    public static void putInt(Context context, String keyint 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(keyfalse); 
    } 
 
    public static void putFloat(Context context, String keyfloat 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 keySet<String> set) { 
        initPreference(context); 
        preferences.putStringSet(keyset); 
        preferences.flush(); 
    } 
 
    /** 
     * @param context 上下文 
     * @param key     键 
     * @return 获取set集合的默认值为:null 
     */ 
    public static Set<String> getStringSet(Context context, String key) { 
        initPreference(context); 
        return preferences.getStringSet(keynull); 
    } 
 
    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