# MyOhosCard **Repository Path**: chinasoft6_ohos/my-ohos-card ## Basic Information - **Project Name**: MyOhosCard - **Description**: 一个简单的在日历上添加日程的应用,我们可以将当日的日程生成卡片到桌面,便于直观查看当日要做事项 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 1 - **Created**: 2021-07-29 - **Last Updated**: 2023-06-26 ## Categories & Tags **Categories**: harmonyos-advanced **Tags**: None ## README # 【中软国际】HarmonyOS 日程提醒卡片应用 #### 一、项目介绍     项目为一个简单的在日历上添加日程的应用,我们可以将当日的日程生成卡片到桌面,便于直观查看当日要做事项。     此项目旨在帮助开发者快速掌握如下技术点:     1) Js卡片的布局、初始化、更新、事件处理;     2) Java卡片的布局、初始化、更新、事件处理。 #### 二、代码结构解读 本教程我们只是对核心代码进行讲解,您可以在最后的参考中下载完整代码,首先来介绍下整个工程的代码结构: ![文件目录](https://images.gitee.com/uploads/images/2021/0727/143045_0a6434c0_8627638.png "directory_structure.png")     1、java-Util:封装处理卡片数据的工具类     2、java-widget:controller—卡片统一管理类;javacard2x4—java2x2与java2x4的卡片初始化、更新等处理类;jscard2x4—js2x2与js2x4的卡片初始化、更新等处理类;widget—js轮播卡片初始化、更新等处理类。     3、java-CardServiceAbility:日程变动,更新卡片的service服务类。     4、java-MainAbility:应用的入口,也是卡片初始化、更新等操作的入口。     5、java-ServiceAbility:js与java的通信服务。     6、js-default-page:index—应用首页,日历日程展示页面代码;schedule—日程添加、修改、删除页面代码。     7、js-jscard2x4:js2x2与js2x4卡片页面代码;js-widget:js轮播卡片页面代码。     8、resources-base-layout:form_grid_pattern_javacard2x4_2_2.xml—java2x2卡片页面代码;form_grid_pattern_javacard2x4_2_4.xml—java2x4卡片页面代码。     9、config.json:java、js、卡片等配置文件。 #### 三、Js轮播卡片 ##### 1.新建js卡片 1)右击—new—service-widget ![输入图片说明](https://images.gitee.com/uploads/images/2021/0727/144246_851ccf1c_8627638.png "create_1.png") 2)选择模板—下一步 ![输入图片说明](https://images.gitee.com/uploads/images/2021/0727/144252_81949e28_8627638.png "create_2.png") 3)输入卡片名称、选择卡片类型(单选)、卡片大小(多选),点击finish即可。 ![输入图片说明](https://images.gitee.com/uploads/images/2021/0727/144302_697bf9e9_8627638.png "create_3.png") ##### 2.默认卡片页面 1)js-widget-pages-index-index.json默认卡片页面的数据     data:默认数据。     actions:routerEvent为router事件,用于应用跳转。 2)js-widget-pages-index-index.css 卡片的样式     同js页面样式开发。 3)js-widget-pages-index-index.html 卡片的页面     同js页面开发,不过要使用卡片支持组件;你可以点击 [JS卡片组件](https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-service-widget-container-badge-0000001153155817) 去学习。     click事件关联json文件中的actions下的应用跳转事件,此处关联的是点击跳转应用的页面,页面代码:on:click="routerEvent"(不可以传参)。 ##### 3.初始化卡片页面 在WidgetImpl.java文件中bindFromData方法中,进行页面展示数据的初始化: ``` public ProviderFormInfo bindFormData() {        ZSONObject zsonObject = new ZSONObject();        ProviderFormInfo providerFormInfo = new ProviderFormInfo();        String scheduleData = StorageUtil.initPreferences(context).getSchedule();        if(!"".equals(scheduleData)) {            ZSONArray array = ZSONArray.stringToZSONArray(scheduleData);            if(array.size()>0) {                ZSONArray result = StorageUtil.getSwiperCardSchedule(array);                CardConstant.SWIPER_CARD_LENGTH = result.size();                zsonObject.put("schedule", result);                zsonObject.put("count", array.size());            }        }        zsonObject.put("index", 0);        providerFormInfo.setJsBindingData(new FormBindingData(zsonObject));        return providerFormInfo;    } ``` ##### 4.更新卡片页面 1)config.json配置定时或周期性刷新     定时或周期性刷新均会调用WidgetImpl.java文件中updateFormData方法,因本卡片未实现此方法,所以不支持此功能;在下面js2x2与2x4卡片中介绍周期性刷新;在下面java2x2与2x4卡片中介绍定时刷新。 2)添加当日日程导致数据变化 ,对日程进行更新介绍     数据变化时,触发数据监听方法,代码在StorageUtil.java工具类,这里开启CardServiceAbility服务,代码如下: ``` private class PreferencesChangeCounter implements Preferences.PreferencesObserver {        private Context mContext;        public PreferencesChangeCounter(Context context){            this.mContext = context;        }        @Override        public void onChange(Preferences preferences, String key) {            Intent intent = new Intent();            Operation operation = new Intent.OperationBuilder()                    .withDeviceId("")                    .withBundleName("com.example.myohoscard")                    .withAbilityName("com.example.myohoscard.CardServiceAbility")                    .build();            intent.setOperation(operation);            mContext.startAbility(intent,0);        }    } ``` CardServiceAbility服务的onCommand方法中进行卡片更新,其更新代码如下: ```    public void swiperData(ZSONArray array){        ZSONArray result = StorageUtil.getSwiperCardSchedule(array);        CardConstant.SWIPER_CARD_LENGTH = result.size();        ZSONObject zsonObject = new ZSONObject();        zsonObject.put("schedule", result);        zsonObject.put("count", array.size());        FormBindingData formBindingData = new FormBindingData(zsonObject);        FormControllerManager formControllerManager = FormControllerManager.getInstance(this);        for(long id:formControllerManager.getAllFormIdFromSharePreference(CardConstant.SWIPER_CARD_FORMS_NAME)) {            try {                updateForm(id, formBindingData);            } catch (FormException e) {                e.printStackTrace();            }        }    } ``` ##### 5.卡片的轮播 CardServiceAbility服务的onCommand方法中调用卡片轮播,其代码如下: ``` public void swiperLoop(){        ZSONObject data = new ZSONObject();        if(CardConstant.SWIPER_CARD_THREAD != null) {            CardConstant.SWIPER_CARD_THREAD.interrupt();        }        CardConstant.SWIPER_CARD_THREAD = new Thread(() -> {            while (true) {                try {                    if(Thread.currentThread().isInterrupted()){                        //处理中断逻辑                        break;                    }                    Thread.sleep(2000);                    data.put("index", index % CardConstant.SWIPER_CARD_LENGTH);                    FormBindingData bindingData = new FormBindingData(data);                    FormControllerManager formControllerManager = FormControllerManager.getInstance(this);                    for(long formid:formControllerManager.getAllFormIdFromSharePreference(CardConstant.SWIPER_CARD_FORMS_NAME)) {                        updateForm(formid, bindingData);                    }                    index++;                    if(index == CardConstant.SWIPER_CARD_LENGTH){                        index = 0;                    }                } catch (Exception e) {                    break;                }            }        });        CardConstant.SWIPER_CARD_THREAD.start();    } ``` 缺点:此处退出应用程序时,service不再运行导致轮播线程结束,轮播失效。 #### 四、Js2x2与2x4卡片 ##### 1.新建卡片页面 同js轮播卡片的新建卡片页面操作步骤,勾选2x2、2x4卡片。 ##### 2.默认卡片页面 1) js-card2x4-pages-index-index.json默认卡片页面的数据     data:默认数据。     actions:routerEvent为router事件,用于应用跳转;sendMessageEvent1与sendMessageEvent2为message事件,用于卡片开发人员自定义点击事件。 2) js-card2x4-pages-index-index.css 卡片的样式     同js页面样式开发。 3) js-card2x4-pages-index-index.html 卡片的页面     同js页面开发,不过要使用卡片支持组件;     2x2或2x4的卡片公用一个html页面,利用dim2X4变量true或者false,来展示2x2或2x4的布局;     click事件关联json文件中的actions下的操作事件,此处关联的是点击跳转应用的页面,代码:on:click="routerEvent"或on:click="sendMessageEvent1"、on:click="sendMessageEvent2"(不支持参数传递)。 ##### 3.初始化卡片页面 在Jscard2x4Impl.java文件中bindFromData方法中,进行页面展示数据的初始化: ``` public ProviderFormInfo bindFormData() {        HiLog.info(TAG, "bind form data");        ZSONObject zsonObject = new ZSONObject();        ProviderFormInfo providerFormInfo = new ProviderFormInfo();        if (dimension == DIMENSION_1X2) {            zsonObject.put("mini", true);        }        if (dimension == DIMENSION_2X4) {            zsonObject.put("dim2X4", true);        }        String scheduleData = StorageUtil.initPreferences(context).getSchedule();        if(!"".equals(scheduleData)) {            ZSONArray array = ZSONArray.stringToZSONArray(scheduleData);            if(array.size()>0) {                zsonObject = getObject(array,zsonObject);            }        }        providerFormInfo.setJsBindingData(new FormBindingData(zsonObject));        return providerFormInfo;    }    /**     * 当天日程存在时,卡片数据展示     * @param array     * @param zsonObject     * @return     */    public static ZSONObject getObject(ZSONArray array,ZSONObject zsonObject){        ZSONArray result = StorageUtil.getCurrentCardSchedule(array);        ZSONObject schedule_current = ZSONObject.stringToZSON(ZSONObject.toZSONString(result.get(0)));        if(schedule_current != null){            zsonObject.put("schedule_current", schedule_current);        }        ZSONObject schedule_1 = ZSONObject.stringToZSON(ZSONObject.toZSONString(result.get(1)));        ZSONObject schedule_2 = ZSONObject.stringToZSON(ZSONObject.toZSONString(result.get(2)));        if(schedule_1 != null){            zsonObject.put("noSchedule", false);            zsonObject.put("hasSchedule1", true);            zsonObject.put("schedule_1", schedule_1);        }        if(schedule_2 != null){            zsonObject.put("hasSchedule2", true);            zsonObject.put("schedule_2", schedule_2);        }        zsonObject.put("count", array.size());        return zsonObject;    } ``` ##### 4.更新卡片页面 1)config.json配置周期性刷新     config.json中jscard2X4卡片配置"updateEnabled": true与"updateDuration": 1,其中updateDuration配置参数为30分钟倍数,此处1即30分钟主动刷新一次卡片。 周期性刷新调用Jscard2x4Impl.java文件中updateFormData方法,代码如下: ``` public void updateFormData(long formId, Object... vars) {        HiLog.info(TAG, "update form data timing, default 30 minutes");        String scheduleData = StorageUtil.initPreferences(context).getSchedule();        if(!"".equals(scheduleData)) {            ZSONArray array = ZSONArray.stringToZSONArray(scheduleData);            if(array.size()>0) {                FormBindingData formBindingData = new FormBindingData(getObject(array,new ZSONObject()));//上一步中有getObject()方法代码                try {                    ((MainAbility)context).updateForm(formId, formBindingData);                } catch (FormException e) {                    e.printStackTrace();                }            }        }    } ``` 2)添加当日日程导致数据变化 ,对日程进行更新介绍     数据变化时,触发数据监听方法,代码在StorageUtil.java工具类中,这里开启CardServiceAbility服务。CardServiceAbility服务的onCommand方法中调用cardData进行卡片更新,其更新代码如下: ``` FormBindingData formBindingData = new FormBindingData(Jscard2X4Impl.getObject(array,new ZSONObject()));//上一步中有getObject()方法代码        FormControllerManager formControllerManager = FormControllerManager.getInstance(this);        try {            for(long id:formControllerManager.getAllFormIdFromSharePreference(CardConstant.JS_CARD_FORMS_NAME)) {                updateForm(id, formBindingData);            }        } catch (FormException e) {            e.printStackTrace();        } ``` ##### 5.router事件与message事件处理 1)router事件     在跳转后的页面,接收参数并进行处理,这里请看MainAbility.java下的onStart方法,代码如下: ``` String strParams = intent.getStringParam("params");        if(strParams != null) {            ZSONObject data = ZSONObject.stringToZSON(strParams);            String message = data.getString("message");            //此处可以根据message判断点击了某个按钮,从而做相应的处理            new ToastDialog(this).setText(message)                    .setAlignment(LayoutAlignment.CENTER).show();        } ``` 2)message事件     在Jscard2x4Impl.java文件中onTriggerFormEvent方法中进行处理,代码如下: ``` public void onTriggerFormEvent(long formId, String message) {        HiLog.info(TAG, "handle card click event.");        ZSONObject data = ZSONObject.stringToZSON(message);        String msg = data.getString("msg");        Intent intent = new Intent();        intent.setParam("params","{\"message\":\""+msg+"\"}");        Operation operation = new Intent.OperationBuilder()                .withDeviceId("")                .withBundleName("com.example.myohoscard")                .withAbilityName("com.example.myohoscard.MainAbility")                .build();        intent.setOperation(operation);        ((MainAbility)context).startAbility(intent);    } ``` #### 五、Java2x2与2x4卡片 ##### 1.新建卡片页面     同js轮播卡片的新建卡片页面操作步骤,勾选type—java、Dimensions—2x2与2x4卡片。 ##### 2.默认卡片页面     resources-base-layout下:java2x2卡片布局页面—form_grid_pattern_javacard2x4_2_2.xml,java2x2卡片布局页面—form_grid_pattern_javacard2x4_2_4.xml;其开发模式同应用java模式页面布局开发。 ##### 3.初始化卡片页面 在Javacard2x4Impl.java文件中bindFromData方法中,进行页面展示数据的初始化: ``` public ProviderFormInfo bindFormData() {        HiLog.info(TAG, "bind form data when create form");        ProviderFormInfo providerFormInfo = new ProviderFormInfo(RESOURCE_ID_MAP.get(dimension), context);        String scheduleData = StorageUtil.initPreferences(context).getSchedule();        if(!"".equals(scheduleData)) {            ZSONArray array = ZSONArray.stringToZSONArray(scheduleData);            if(array.size()>0) {                // ComponentProvider表示Java卡片                ComponentProvider componentProvider = new ComponentProvider();                providerFormInfo.mergeActions(getFormDataCreate(array,context,dimension,componentProvider));            }        }        return providerFormInfo;    } public static ComponentProvider getFormDataCreate(ZSONArray array,Context context,int dimension,ComponentProvider componentProvider){        componentProvider.setVisibility(ResourceTable.Id_count, Text.VISIBLE);        componentProvider.setText(ResourceTable.Id_count, array.size()+"");        ZSONArray result = StorageUtil.getCurrentCardSchedule(array);        ZSONObject schedule_current = ZSONObject.stringToZSON(ZSONObject.toZSONString(result.get(0)));        if(schedule_current != null){            componentProvider.setText(ResourceTable.Id_time, schedule_current.getString("startCardShow")+"-"+schedule_current.getString("endCardShow"));            componentProvider.setText(ResourceTable.Id_schedule, schedule_current.getString("title"));        }        if (dimension == DEFAULT_DIMENSION_2X2) {            componentProvider.setIntentAgent(ResourceTable.Id_layout, getStartAbilityIntentAgent(context));        }        if (dimension == DIMENSION_2X4) {            componentProvider.setVisibility(ResourceTable.Id_noschedule, Text.HIDE);            ZSONObject schedule_1 = ZSONObject.stringToZSON(ZSONObject.toZSONString(result.get(1)));            if(schedule_1 != null){ //                componentProvider.setText(ResourceTable.Id_time1, schedule_1.getString("startCardShow")+"-"+schedule_1.getString("endCardShow"));                componentProvider.setText(ResourceTable.Id_schedule1, schedule_1.getString("title"));            }            ZSONObject schedule_2 = null;            if(result.get(2) != null){                schedule_2 = ZSONObject.stringToZSON(ZSONObject.toZSONString(result.get(2)));            }            if(schedule_2 != null){                componentProvider.setVisibility(ResourceTable.Id_line, Text.VISIBLE); //                componentProvider.setText(ResourceTable.Id_time2, schedule_2.getString("startCardShow")+"-"+schedule_2.getString("endCardShow"));                componentProvider.setText(ResourceTable.Id_schedule2, schedule_2.getString("title"));            }        }        if (dimension == DIMENSION_2X4) {            componentProvider.setIntentAgent(ResourceTable.Id_more, getStartAbilityIntentAgent(context));        }        return componentProvider;    } ``` ##### 4.更新卡片页面 1)config.json配置定时刷新     config.json中jscard2X4卡片配置"scheduledUpdateTime": "10:42"与"updateDuration": 0,其中scheduledUpdateTime配置参数为24小时制,可以在当天指定时间对卡片进行刷新。     定时刷新调用Javacard2x4Impl.java文件中updateFormData方法,代码如下: ``` public void updateFormData(long formId, Object... vars) {        HiLog.info(TAG, "update form data timing, default 30 minutes");        String scheduleData = StorageUtil.initPreferences(context).getSchedule();        if(!"".equals(scheduleData)) {            ZSONArray array = ZSONArray.stringToZSONArray(scheduleData);            if(array.size()>0) {                ComponentProvider componentProvider =                        new ComponentProvider(RESOURCE_ID_MAP.get(dimension), context);                try {                    ((MainAbility)context).updateForm(formId, getFormDataUpdate(array,context,dimension,componentProvider));                } catch (FormException e) {                    HiLog.error(TAG, e.toString());                }            }        }    } public static ComponentProvider getFormDataUpdate(ZSONArray array,Context context,int dimension,ComponentProvider componentProvider){        componentProvider.setVisibility(ResourceTable.Id_count, Text.VISIBLE);        componentProvider.setText(ResourceTable.Id_count, array.size()+"");        ZSONArray result = StorageUtil.getCurrentCardSchedule(array);        ZSONObject schedule_current = ZSONObject.stringToZSON(ZSONObject.toZSONString(result.get(0)));        if(schedule_current != null){            componentProvider.setText(ResourceTable.Id_time, schedule_current.getString("startCardShow")+"-"+schedule_current.getString("endCardShow"));            componentProvider.setText(ResourceTable.Id_schedule, schedule_current.getString("title"));        }        if (dimension == DIMENSION_2X4) {            componentProvider.setVisibility(ResourceTable.Id_noschedule, Text.HIDE);            ZSONObject schedule_1 = ZSONObject.stringToZSON(ZSONObject.toZSONString(result.get(1)));            if(schedule_1 != null){ //                componentProvider.setText(ResourceTable.Id_time1, schedule_1.getString("startCardShow")+"-"+schedule_1.getString("endCardShow"));                componentProvider.setText(ResourceTable.Id_schedule1, schedule_1.getString("title"));            }            ZSONObject schedule_2 = null;            if(result.get(2) != null){                schedule_2 = ZSONObject.stringToZSON(ZSONObject.toZSONString(result.get(2)));            }            if(schedule_2 != null){                componentProvider.setVisibility(ResourceTable.Id_line, Text.VISIBLE); //                componentProvider.setText(ResourceTable.Id_time2, schedule_2.getString("startCardShow")+"-"+schedule_2.getString("endCardShow"));                componentProvider.setText(ResourceTable.Id_schedule2, schedule_2.getString("title"));            }        }        return componentProvider;    } ``` 2)添加当日日程导致数据变化 ,对日程进行更新介绍 数据变化时,触发数据监听方法,代码在StorageUtil.java工具类中,这里开启CardServiceAbility服务。CardServiceAbility服务的onCommand方法中调用cardData进行卡片更新,其更新代码如下: ``` try {            for(long id:formControllerManager.getAllFormIdFromSharePreference(CardConstant.JAVA_CARD_FORMS_NAME)) {                int dimension = formControllerManager.getDimension(id+"");                ComponentProvider componentProvider =                    new ComponentProvider(Javacard2x4Impl.RESOURCE_ID_MAP.get(dimension), this);                updateForm(id, Javacard2x4Impl.getFormDataUpdate(array,this,dimension,componentProvider));//上一步中有此方法代码            }        } catch (FormException e) {            e.printStackTrace();        } ``` ##### 5.卡片点击事件添加 给控件点击点击事件,其代码为:componentProvider.setIntentAgent(ResourceTable.Id_more, getStartAbilityIntentAgent(context)),其中getStartAbilityIntentAgent方法代码如下: ``` private static IntentAgent getStartAbilityIntentAgent(Context context) {        Intent intent = new Intent();        Operation operation = new Intent.OperationBuilder()                .withDeviceId("")                .withBundleName("com.example.myohoscard")                .withAbilityName("com.example.myohoscard.MainAbility")                .build();        intent.setOperation(operation);        List intentList = new ArrayList<>();        intentList.add(intent);        List flags = new ArrayList<>();        flags.add(IntentAgentConstant.Flags.UPDATE_PRESENT_FLAG);        IntentAgentInfo paramsInfo =                new IntentAgentInfo(200, IntentAgentConstant.OperationType.START_ABILITY, flags, intentList, null);        return IntentAgentHelper.getIntentAgent(context, paramsInfo);    } ``` #### 六、最终实现效果 ![实现效果](https://images.gitee.com/uploads/images/2021/0727/143055_cad3de7a_8627638.gif "card_demo.gif") #### 七、代码参考 [https://gitee.com/chinasoft6_ohos/my-ohos-card](https://gitee.com/chinasoft6_ohos/my-ohos-card) 作者:陈巧银