README.md
1# 桌面卡片进入案例
2
3### 介绍
4
5桌面卡片是比较常见的功能,本案例详细列举了卡片开发的大部分功能,如使用postCardAction接口快速拉起卡片提供方应用的指定UIAbility,通过message事件刷新卡片内容等,为开发者提供了卡片功能的展示。
6
7### 效果预览
8
9| 卡片 | 案例|
10|--------|-----------|
11| <img src="screenshots/devices/Widget.jpg" width="270" /> | <img src="screenshots/devices/Case.jpg" width="270" />|
12
13### 使用说明
14
151. 长按应用,添加卡片到桌面。
162. 卡片内可滑动选择案例,点击可进入案例详情。
17
18### 实现思路
19
201. 新建卡片
212. 配置formconfig
223. 编写卡片UI代码
234. 触发刷新事件
245. 触发点击事件
25
26### 实现步骤
27
28本例涉及的关键特性和实现方案如下:
29
301. 新建卡片。右键点击entry目录,选择新建->Service Widget->Dynamic Widget,其中Dynamic Widget为动态卡片,Static Widget为静态卡片。此时会生成几个文件:配置文件[```form_config.json```](entry/src/main/resources/base/profile/form_config.json);卡片Ability[```EntryFormAbility.ets```](entry/src/main/ets/entryformability/EntryFormAbility.ets);卡片组件[```CasesSwiperCard.ets```](entry/src/main/ets/widget/pages/CasesSwiperCard.ets)。
31
322. 新建卡片后,根据需要(如卡片大小,刷新时间,动态静态卡片设置)配置```form_config.json```。
33
34```json5
35{
36 "forms": [
37 {
38 "name": "widget", // 卡片的名称。
39 "displayName": "$string:widget_display_name", // 卡片的显示名称。
40 "description": "$string:widget_desc", // 卡片的描述。
41 "src": "./ets/widget/pages/CasesSwiperCard.ets", // 卡片对应的UI代码的完整路径。
42 "uiSyntax": "arkts", // 卡片的类型
43 "window": { // 用于定义与显示窗口相关的配置。
44 "designWidth": 720,
45 "autoDesignWidth": true
46 },
47 "colorMode": "auto", // 卡片的主题样式。
48 "isDynamic": true, // 卡片是否为动态卡片。
49 "isDefault": true, // 卡片是否为默认卡片。
50 "updateEnabled": true, // 卡片是否支持周期性刷新。
51 "scheduledUpdateTime": "10:30", // 卡片的定点刷新的时刻。
52 "updateDuration": 1, // 卡片定时刷新的更新周期,单位为30分钟,取值为自然数。
53 "defaultDimension": "6*4", // 卡片的默认外观规格。
54 "supportDimensions": [ // 卡片支持的外观规格,取值范围。
55 "6*4"
56 ]
57 }
58 ]
59}
60```
61
623. 编写卡片UI代码。在主文件```CasesSwiperCard.ets```中添加UI组件,需要注意的是:ArkTS卡片存在较多约束(如不支持导入共享包),较多逻辑不可在卡片中使用,在使用时需要根据文档进行操作。
63
644. 编写跳转事件:当应用未被拉起时,点击某个卡片时跳转到具体的案例页面。在```EntryAbility.ets```中补充逻辑:onCreate生命周期内获取want.parameters.params判断卡片内容的跳转。
65
66```typescript
67// EntryAbility.ets
68export default class EntryAbility extends UIAbility {
69 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
70 // ...
71 // 桌面卡片判断跳转内容
72 if (want?.parameters?.params) {
73 // want.parameters.params 对应 postCardAction() 中 params 内容
74 let params: Record<string, Object> = JSON.parse(want.parameters.params as string);
75 this.selectPage = params.targetPage as string;
76 }
77 // ...
78 }
79}
80```
81
825. 编写跳转事件:当应用在后台时,点击某个卡片时跳转到具体的案例页面。可从onNewWant生命周期获取want.parameters.params判断卡片内容的跳转。
83
84```typescript
85// EntryAbility.ets
86export default class EntryAbility extends UIAbility {
87 // 如果UIAbility已在后台运行,在收到Router事件后会触发onNewWant生命周期回调
88 onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
89 if (want.parameters!['ability.params.stream'] !== undefined) {
90 AppStorage.setOrCreate('imageUri', want.parameters!["ability.params.stream"].toString());
91 return;
92 }
93 if (want?.parameters?.params) {
94 // want.parameters.params 对应 postCardAction() 中 params 内容
95 let params: Record<string, Object> = JSON.parse(want.parameters.params as string);
96 this.selectPage = params.targetPage as string;
97 if (this.currentWindowStage !== null) {
98 // 存在窗口时点击卡片后进行页面跳转
99 this.currentWindowStage.loadContent(`pages/${this.selectPage}`, (err) => {
100 if (err.code) {
101 hilog.error(0x0000, 'testTagSelectPage', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
102 return;
103 }
104 hilog.info(0x0000, 'testTagSelectPage', 'Succeeded in loading the content.');
105 });
106 this.selectPage = '';
107 }
108 } else {
109 this.selectPage = '';
110 }
111 }
112}
113```
114
1156. 具体跳转逻辑编写。在onWindowStageCreate生命周期内进行具体的跳转逻辑。
116
117```typescript
118// EntryAbility.ets
119export default class EntryAbility extends UIAbility {
120 onWindowStageCreate(windowStage: window.WindowStage): void {
121 // Main window is created, set main page for this ability
122 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
123 // 判断是否存在窗口可进行页面跳转
124 if (this.currentWindowStage === null) {
125 this.currentWindowStage = windowStage;
126 }
127 // 点击卡片后进行页面跳转
128 if (this.selectPage) {
129 windowStage.loadContent(`pages/${this.selectPage}`, (err) => {
130 if (err.code) {
131 hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
132 return;
133 }
134 hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
135 });
136 } else {
137 windowStage.loadContent('pages/Index', (err) => {
138 if (err.code) {
139 hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
140 return;
141 }
142 hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
143 });
144 }
145 }
146}
147```
148
1497. 编写刷新事件:当定时更新或定点更新触发时,需要更新卡片内容。onUpdateForm生命周期发生在定时更新/定点更新/卡片使用方主动请求更新时,在方法内增加获取案例数据的功能。
150
151```typescript
152// EntryFormAbility.ets
153export default class EntryFormAbility extends FormExtensionAbility {
154 // 获取数据并利用formProvider.updateForm更新到卡片
155 async getData(formId: string) {
156 let detail: CASES[] = [];
157 let message = '';
158 try {
159 let value = await this.context.resourceManager.getRawFileContent('CasesData.txt');
160 console.log('decodeToStringdecodeToString')
161 message = util.TextDecoder.create().decodeToString(value);
162 detail = CaseCardUtils.formatData(message);
163 hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onFormEvent' + 'result:' + message);
164 hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] getData' + 'detail:' + JSON.stringify(detail));
165 class FormDataClass {
166 public detail: CASES[] = detail;
167 }
168 let formData = new FormDataClass();
169 let formInfo = formBindingData.createFormBindingData(formData);
170 await formProvider.updateForm(formId, formInfo);
171 } catch (error) {
172 hilog.error(DOMAIN_NUMBER, TAG, `FormAbility updateForm failed: ${JSON.stringify(error)}`);
173 }
174 }
175
176 async onUpdateForm(formId: string): Promise<void> {
177 // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
178 hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onUpdateForm');
179 this.getData(formId);
180 }
181}
182```
183
1848. 编写刷新事件:参数传到卡片组件内,组件接收参数。处理```CasesSwiperCard.ets```卡片内逻辑。卡片页面中使用LocalStorageProp装饰需要刷新的卡片数据。
185
186```ts
187let casesCardInfo = new LocalStorage();
188@Entry(casesCardInfo)
189@Component
190struct Widget_DynamicCard {
191 @LocalStorageProp('detail') detail: CASES[] = []; // 卡片对象集合
192 private swiperController: SwiperController = new SwiperController();
193
194 build() {
195 // ...
196 }
197}
198```
199
200### 工程结构&模块类型
201
202```
203CardInteractionCase // har
204|---entryability
205| |---EntryAbility.ets // EntryAbility
206|---entryformability
207| |---EntryFormAbility.ets //EntryFormAbility
208|---pages
209| |---DealStrideSolution.ets // 解决相机预览花屏案例页面
210| |---EncapsulationDialog.ets // 弹窗封装案例页面
211| |---Index.ets // 首页页面
212| |---ShareButton.ets // 分享二维码按钮案例页面
213| |---SmartFill.ets // 智能填充案例页面
214| |---VideoTrimmer.ets // 视频下载保存及剪辑压缩上传案例页面
215|---widget
216| |---pages
217| |---CasesSwiperCard.ets // 卡片页面
218| |---utils
219| |---CaseCardUtils.ets // 卡片公共方法页面
220```
221
222### 参考资料
223
224[创建一个ArkTS卡片](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/form/arkts-ui-widget-creation.md)
225
226[配置卡片的配置文件](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/form/arkts-ui-widget-configuration.md)
227
228[使用router事件跳转到指定UIAbility](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/form/arkts-ui-widget-event-router.md)
229
230[通过message事件刷新卡片内容](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/form/arkts-ui-widget-event-formextensionability.md)
231
232### 依赖
233
234不涉及。
235
236### 约束与限制
237
2381.本示例仅支持标准系统上运行。
239
2402.本示例已适配API version 12版本SDK。
241
2423.本示例需要使用DevEco Studio 5.0.0 Release及以上版本才可编译运行。
243
244### 下载
245
246如需单独下载本工程,执行如下命令:
247
248```
249git init
250git config core.sparsecheckout true
251echo code/SuperFeature/Widget/ArkTSCard/CardInteractionCase/ > .git/info/sparse-checkout
252git remote add origin https://gitee.com/openharmony/applications_app_samples.git
253git pull origin master
254```
255