最近看到很多博客网站上出现了HarmonyOS的征文活动,看到那些精美的奖品让我也安耐不住开了,当然奖品的诱惑当然是抵挡不住我对技术的狂热追求,对于开发者而言技术没有顶峰没有终点。那么今天给大家做一个卡片服务开发的经验分享,如果有什么地方说的不对的请各位开发者进行指正,如果有什么问题也可以发私信或者直接在帖子中留言,我也会及时回复大家。
服务卡片(以下简称“卡片”)是FA的一种界面展示形式,将FA的重要信息或操作直接放置到卡片中,用户通过直接操作卡片就可以达到应用的使用体验,这样做大大减少了应用的使用层级性。
卡片常用于嵌入到其他应用中作为其界面的一部分显示(也可以使用原子化服务将应用保存到服务中心中,这种方式不需要安装应用),并支持拉起页面,发送消息等基础的交互功能。
原子化服务在下个帖子中介绍。
示例如下图所示。
为了开发者能够便于理解,官方将服务卡片分为三方面:
卡片使用方和提供方不要求常驻运行,在需要添加/删除/请求更新卡片时,卡片管理服务会拉起卡片提供方获取卡片信息。
文字描述滞后,先上图。(图片由官网提供,此处借用一下)
从图中可以清楚的看到服务卡片的整体通信层都是由"RPC"负责,通过通信适配层构成了数据发送接收的通道。
卡片管理服务
卡片提供方
因为要使用java语言编写,所以此处要选择支持Java语言的空页面。
选择成功进入新建项目配置界面。
点击Finish进入项目。
创建模板:右击“Entry”→“New”→“Server Widget”
设置模板
项目目录
由图可见:添加卡片模板后在原来项目中增加一个widget文件夹,并在文件夹中出现了三个文件FormControllerManager.java、FormController.java、CardWidgetImpl.java。三个文件对应了卡片服务的运行机制,通过关系的对应可以清楚的了解到代码运行原理。
FormControllerManager.java(卡片控制器管理【卡片管理服务】)
public class FormControllerManager {
private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, FormControllerManager.class.getName());
private static final String PACKAGE_PATH = "com.example.carddemo.widget";
private static final String SHARED_SP_NAME = "form_info_sp.xml";
private static final String FORM_NAME = "formName";
private static final String DIMENSION = "dimension";
private static FormControllerManager managerInstance = null;
private final HashMap<Long, FormController> controllerHashMap = new HashMap<>();
private final Context context;
private final Preferences preferences;
/**
* 初始化构造器
*
* @param context instance of Context.
*/
private FormControllerManager(Context context) {
this.context = context;
DatabaseHelper databaseHelper = new DatabaseHelper(this.context.getApplicationContext());
preferences = databaseHelper.getPreferences(SHARED_SP_NAME);
}
/**
* FormControllerManager 实例化
*
* @param context instance of Context.
* @return FormControllerManager instance.
*/
public static FormControllerManager getInstance(Context context) {
if (managerInstance == null) {
synchronized (FormControllerManager.class) {
if (managerInstance == null) {
managerInstance = new FormControllerManager(context);
}
}
}
return managerInstance;
}
/**
* 通过构造器将传入的卡片信息进行封装,返回卡片服务
*
* @param formId form id.
* @param formName form name.
* @param dimension form dimension
* @return FormController form controller
*/
public FormController createFormController(long formId, String formName, int dimension) {
synchronized (controllerHashMap) {
if (formId < 0 || formName.isEmpty()) {
return null;
}
HiLog.info(TAG,
"saveFormId() formId: " + formId + ", formName: " + formName + ", preferences: " + preferences);
if (preferences != null) {
ZSONObject formObj = new ZSONObject();
formObj.put(FORM_NAME, formName);
formObj.put(DIMENSION, dimension);
preferences.putString(Long.toString(formId), ZSONObject.toZSONString(formObj));
preferences.flushSync();
}
// Create controller instance.
FormController controller = newInstance(formName, dimension, context);
// Cache the controller.
if (controller != null) {
if (!controllerHashMap.containsKey(formId)) {
controllerHashMap.put(formId, controller);
}
}
return controller;
}
}
/**
* 获取卡片控制器实例
*
* @param formId form id.
* @return the instance of form controller.
*/
public FormController getController(long formId) {
synchronized (controllerHashMap) {
if (controllerHashMap.containsKey(formId)) {
return controllerHashMap.get(formId);
}
Map<String, ?> forms = preferences.getAll();
String formIdString = Long.toString(formId);
if (forms.containsKey(formIdString)) {
ZSONObject formObj = ZSONObject.stringToZSON((String) forms.get(formIdString));
String formName = formObj.getString(FORM_NAME);
int dimension = formObj.getIntValue(DIMENSION);
FormController controller = newInstance(formName, dimension, context);
controllerHashMap.put(formId, controller);
}
return controllerHashMap.get(formId);
}
}
private FormController newInstance(String formName, int dimension, Context context) {
FormController ctrInstance = null;
if (formName == null || formName.isEmpty()) {
HiLog.error(TAG, "newInstance() get empty form name");
return ctrInstance;
}
try {
String className = PACKAGE_PATH + "." + formName.toLowerCase(Locale.ROOT) + "."
+ getClassNameByFormName(formName);
Class<?> clazz = Class.forName(className);
if (clazz != null) {
Object controllerInstance = clazz.getConstructor(Context.class, String.class, Integer.class)
.newInstance(context, formName, dimension);
if (controllerInstance instanceof FormController) {
ctrInstance = (FormController) controllerInstance;
}
}
} catch (NoSuchMethodException | InstantiationException | IllegalArgumentException | InvocationTargetException
| IllegalAccessException | ClassNotFoundException | SecurityException exception) {
HiLog.error(TAG, "newInstance() get exception: " + exception.getMessage());
}
return ctrInstance;
}
/**
* 从数组中获取所有卡片id
*
* @return form id list
*/
public List<Long> getAllFormIdFromSharePreference() {
List<Long> result = new ArrayList<>();
Map<String, ?> forms = preferences.getAll();
for (String formId : forms.keySet()) {
result.add(Long.parseLong(formId));
}
return result;
}
/**
* 删除卡片服务
*
* @param formId form id
*/
public void deleteFormController(long formId) {
synchronized (controllerHashMap) {
preferences.delete(Long.toString(formId));
preferences.flushSync();
controllerHashMap.remove(formId);
}
}
private String getClassNameByFormName(String formName) {
String[] strings = formName.split("_");
StringBuilder result = new StringBuilder();
for (String string : strings) {
result.append(string);
}
char[] charResult = result.toString().toCharArray();
charResult[0] = (charResult[0] >= 'a' && charResult[0] <= 'z') ? (char) (charResult[0] - 32) : charResult[0];
return String.copyValueOf(charResult) + "Impl";
}
}
FormController.java(卡片控制器【卡片提供方】)
public abstract class FormController {
protected final Context context;
protected final String formName;
protected final int dimension;
public FormController(Context context, String formName, Integer dimension) {
this.context = context;
this.formName = formName;
this.dimension = dimension;
}
/**
* 创建卡片信息提供者
*/
public abstract ProviderFormInfo bindFormData();
/**
* 更新卡片信息
*/
public abstract void updateFormData(long formId, Object... vars);
/**
* 在接收服务小部件消息事件时调用
*/
public abstract void onTriggerFormEvent(long formId, String message);
/**
* 获取路由的目标界面
*/
public abstract Class<? extends AbilitySlice> getRoutePageSlice(Intent intent);
}
CardWidgetImpl.java(卡片应用【卡片使用方】)
public class CardWidgetImpl extends FormController {
public static final int DIMENSION_1X2 = 1;
public static final int DIMENSION_2X4 = 3;
private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, CardWidgetImpl.class.getName());
private static final int DEFAULT_DIMENSION_2X2 = 2;
private static final Map<Integer, Integer> RESOURCE_ID_MAP = new HashMap<>();
static {
RESOURCE_ID_MAP.put(DIMENSION_1X2, ResourceTable.Layout_form_grid_pattern_cardwidget_1_2);
RESOURCE_ID_MAP.put(DEFAULT_DIMENSION_2X2, ResourceTable.Layout_form_grid_pattern_cardwidget_2_2);
RESOURCE_ID_MAP.put(DIMENSION_2X4, ResourceTable.Layout_form_grid_pattern_cardwidget_2_4);
}
public CardWidgetImpl(Context context, String formName, Integer dimension) {
super(context, formName, dimension);
}
//创建好卡片服务后,在界面展示卡片
@Override
public ProviderFormInfo bindFormData() {
HiLog.info(TAG, "bind form data when create form");
return new ProviderFormInfo(RESOURCE_ID_MAP.get(dimension), context);
}
//更新卡片信息
@Override
public void updateFormData(long formId, Object... vars) {
HiLog.info(TAG, "update form data timing, default 30 minutes");
}
//卡片中内容手势触发方法
@Override
public void onTriggerFormEvent(long formId, String message) {
HiLog.info(TAG, "handle card click event.");
}
@Override
public Class<? extends AbilitySlice> getRoutePageSlice(Intent intent) {
HiLog.info(TAG, "get the default page to route when you click card.");
return null;
}
}
MainAbility的变化,新增了一些方法。
public class MainAbility extends Ability {
public static final int DEFAULT_DIMENSION_2X2 = 2;
public static final int DIMENSION_1X2 = 1;
public static final int DIMENSION_2X4 = 3;
public static final int DIMENSION_4X4 = 4;
private static final int INVALID_FORM_ID = -1;
private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, MainAbility.class.getName());
private String topWidgetSlice;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setMainRoute(MainAbilitySlice.class.getName());
if (intentFromWidget(intent)) {
topWidgetSlice = getRoutePageSlice(intent);
if (topWidgetSlice != null) {
setMainRoute(topWidgetSlice);
}
}
stopAbility(intent);
}
//创建卡片信息并返回卡片内容
@Override
protected ProviderFormInfo onCreateForm(Intent intent) {
HiLog.info(TAG, "onCreateForm");
long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
int dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
HiLog.info(TAG, "onCreateForm: formId=" + formId + ",formName=" + formName);
FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
FormController formController = formControllerManager.getController(formId);
//通过调用布局Id将卡片布局与卡片信息进行绑定
formController = (formController == null) ? formControllerManager.createFormController(formId,
formName, dimension) : formController;
if (formController == null) {
HiLog.error(TAG, "Get null controller. formId: " + formId + ", formName: " + formName);
return null;
}
return formController.bindFormData();
}
//更新卡片信息
@Override
protected void onUpdateForm(long formId) {
HiLog.info(TAG, "onUpdateForm");
super.onUpdateForm(formId);
FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
FormController formController = formControllerManager.getController(formId);
formController.updateFormData(formId);
}
//删除卡片
@Override
protected void onDeleteForm(long formId) {
HiLog.info(TAG, "onDeleteForm: formId=" + formId);
super.onDeleteForm(formId);
FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
formControllerManager.deleteFormController(formId);
}
//卡片手势触发
@Override
protected void onTriggerFormEvent(long formId, String message) {
HiLog.info(TAG, "onTriggerFormEvent: " + message);
super.onTriggerFormEvent(formId, message);
FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
FormController formController = formControllerManager.getController(formId);
formController.onTriggerFormEvent(formId, message);
}
@Override
public void onNewIntent(Intent intent) {
if (intentFromWidget(intent)) { // Only response to it when starting from a service widget.
String newWidgetSlice = getRoutePageSlice(intent);
if (topWidgetSlice == null || !topWidgetSlice.equals(newWidgetSlice)) {
topWidgetSlice = newWidgetSlice;
restart();
}
}
}
private boolean intentFromWidget(Intent intent) {
long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
return formId != INVALID_FORM_ID;
}
//跳转至对应的卡片界面
private String getRoutePageSlice(Intent intent) {
long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
if (formId == INVALID_FORM_ID) {
return null;
}
FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
FormController formController = formControllerManager.getController(formId);
if (formController == null) {
return null;
}
Class<? extends AbilitySlice> clazz = formController.getRoutePageSlice(intent);
if (clazz == null) {
return null;
}
return clazz.getName();
}
}
配置文件
属性名称 | 子属性名称 | 含义 | 数据类型 | |
---|---|---|---|---|
name | - | 表示卡片的类名。字符串最大长度为127字节。 | 字符串 | |
description | - | 表示卡片的描述。取值可以是描述性内容,也可以是对描述性内容的资源索引,以支持多语言。字符串最大长度为255字节。 | 字符串 | |
isDefault | - | 表示该卡片是否为默认卡片,每个Ability有且只有一个默认卡片。 true:默认卡片。false:非默认卡片。 | 布尔值 | |
type | - | 表示卡片的类型。取值范围如下: Java:Java卡片。JS:JS卡片。 | 字符串 | |
colorMode | - | 表示卡片的主题样式,取值范围如下: auto:自适应。dark:深色主题。light:浅色主题。 | 字符串 | |
supportDimensions | - | 表示卡片支持的外观规格,取值范围: 1*2:表示1行2列的二宫格。2*2:表示2行2列的四宫格。2*4:表示2行4列的八宫格。4*4:表示4行4列的十六宫格。 | 字符串数组 | |
defaultDimension | - | 表示卡片的默认外观规格,取值必须在该卡片supportDimensions配置的列表中。 | 字符串 | |
landscapeLayouts | - | 表示卡片外观规格对应的横向布局文件,与supportDimensions中的规格一一对应。 仅当卡片类型为Java卡片时,需要配置该标签。 | 字符串数组 | |
portraitLayouts | - | 表示卡片外观规格对应的竖向布局文件,与supportDimensions中的规格一一对应。 仅当卡片类型为Java卡片时,需要配置该标签。 | 字符串数组 | |
updateEnabled | - | 表示卡片是否支持周期性刷新,取值范围: true:表示支持周期性刷新,可以在定时刷新(updateDuration)和定点刷新(scheduledUpdateTime)两种方式任选其一,优先选择定时刷新。false:表示不支持周期性刷新。 | 布尔类型 | |
scheduledUpdateTime | - | 表示卡片的定点刷新的时刻,采用24小时制,精确到分钟。 | 字符串 | |
updateDuration | - | 表示卡片定时刷新的更新周期,单位为30分钟,取值为自然数。 当取值为0时,表示该参数不生效。当取值为正整数N时,表示刷新周期为30*N分钟。 | 数值 | |
formConfigAbility | - | 表示卡片的配置跳转链接,采用URI格式。 | 字符串 | |
jsComponentName | - | 表示JS卡片的Component名称。字符串最大长度为127字节。 仅当卡片类型为JS卡片时,需要配置该标签。 | 字符串 | |
metaData | - | 表示卡片的自定义信息,包含customizeData数组标签。 | 对象 | |
customizeData | - | 表示自定义的卡片信息。 | 对象数组 | |
name | 表示数据项的键名称。字符串最大长度为255字节。 | 字符串 | ||
value | 表示数据项的值。字符串最大长度为255字节。 | 字符串 |
布尔值 type - 表示卡片的类型。取值范围如下:
字符串 colorMode - 表示卡片的主题样式,取值范围如下:
字符串 supportDimensions - 表示卡片支持的外观规格,取值范围:
字符串数组 defaultDimension - 表示卡片的默认外观规格,取值必须在该卡片supportDimensions配置的列表中。 字符串 landscapeLayouts - 表示卡片外观规格对应的横向布局文件,与supportDimensions中的规格一一对应。 仅当卡片类型为Java卡片时,需要配置该标签。 字符串数组 portraitLayouts - 表示卡片外观规格对应的竖向布局文件,与supportDimensions中的规格一一对应。 仅当卡片类型为Java卡片时,需要配置该标签。 字符串数组 updateEnabled - 表示卡片是否支持周期性刷新,取值范围:
布尔类型 scheduledUpdateTime - 表示卡片的定点刷新的时刻,采用24小时制,精确到分钟。 字符串 updateDuration - 表示卡片定时刷新的更新周期,单位为30分钟,取值为自然数。
数值 formConfigAbility - 表示卡片的配置跳转链接,采用URI格式。 字符串 jsComponentName - 表示JS卡片的Component名称。字符串最大长度为127字节。 仅当卡片类型为JS卡片时,需要配置该标签。 字符串 metaData - 表示卡片的自定义信息,包含customizeData数组标签。 对象 customizeData - 表示自定义的卡片信息。 对象数组 name 表示数据项的键名称。字符串最大长度为255字节。 字符串 value 表示数据项的值。字符串最大长度为255字节。 字符串
分别修改form_grid_pattern_cardwidget_1_2、form_grid_pattern_cardwidget_2_2.xml和form_grid_pattern_cardwidget_2_4.xml文件。
form_grid_pattern_cardwidget_1_2.xml
<?xml version="1.0" encoding="utf-8"?>
<DependentLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:remote="true">
<Image
ohos:height="match_parent"
ohos:width="match_parent"
ohos:background_element="#1281f0"
ohos:scale_mode="clip_center"/>
<DirectionalLayout
ohos:height="match_parent"
ohos:width="match_parent"
ohos:alignment="center"
ohos:orientation="horizontal">
<Text
ohos:id="$+id:tv_name"
ohos:height="match_content"
ohos:width="match_content"
ohos:text="$string:cardwidget_title"
ohos:text_color="#E5FFFFFF"
ohos:text_size="16fp"
ohos:text_weight="1200"/>
<Text
ohos:height="match_content"
ohos:width="match_content"
ohos:text="3350"
ohos:text_color="#E5FFFFFF"
ohos:text_size="16fp"
ohos:text_weight="1200"/>
</DirectionalLayout>
</DependentLayout>
form_grid_pattern_cardwidget_2_2.xml
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:background_element="#FFFFFFFF"
ohos:remote="true"
ohos:orientation="vertical">
<DirectionalLayout
ohos:id="$+id:tv_top"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:weight="1"
ohos:orientation="horizontal">
<DirectionalLayout
ohos:height="match_parent"
ohos:width="match_parent"
ohos:weight="1"
ohos:orientation="vertical"
ohos:alignment="center">
<Text
ohos:height="match_content"
ohos:width="match_content"
ohos:text="消耗热量"
ohos:top_margin="5vp"
ohos:text_size="10fp"
ohos:text_weight="500">
</Text>
<Text
ohos:height="60vp"
ohos:width="60vp"
ohos:text="2.1 Kcal"
ohos:text_alignment="center"
ohos:text_color="#1281f0"
ohos:background_element="$media:aa_1"
ohos:text_size="10fp"
ohos:text_weight="500"/>
</DirectionalLayout>
<DirectionalLayout
ohos:height="match_parent"
ohos:width="match_parent"
ohos:weight="1"
ohos:orientation="vertical"
ohos:alignment="center">
<Text
ohos:height="match_content"
ohos:width="match_content"
ohos:text="实时心率"
ohos:top_margin="5vp"
ohos:text_size="10fp"
ohos:text_weight="500">
</Text>
<Text
ohos:height="60vp"
ohos:width="60vp"
ohos:text="99/min"
ohos:text_alignment="center"
ohos:text_color="#1281f0"
ohos:background_element="$media:aa_1"
ohos:text_size="10fp"
ohos:text_weight="500"/>
</DirectionalLayout>
</DirectionalLayout>
<DirectionalLayout
ohos:id="$+id:tv_blow"
ohos:height="60vp"
ohos:width="match_parent"
ohos:align_parent_bottom="true"
ohos:background_element="#1281f0"
ohos:alignment="center"
ohos:orientation="vertical">
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_parent"
ohos:alignment="center"
ohos:orientation="horizontal">
<Text
ohos:id="$+id:tv_name"
ohos:height="match_content"
ohos:width="match_content"
ohos:text="$string:cardwidget_title"
ohos:text_color="#E5FFFFFF"
ohos:text_size="16fp"
ohos:text_weight="600"/>
<Text
ohos:height="match_content"
ohos:width="match_content"
ohos:text="3350"
ohos:text_color="#E5FFFFFF"
ohos:text_size="16fp"
ohos:text_weight="1000"/>
</DirectionalLayout>
<Text
ohos:height="match_content"
ohos:width="match_content"
ohos:text="$string:cardwidget_introduction"
ohos:text_color="#ffffff"
ohos:text_size="14fp"
ohos:text_weight="800"
ohos:top_margin="7vp"/>
</DirectionalLayout>
</DirectionalLayout>
form_grid_pattern_cardwidget_2_4.xml
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:background_element="#FFFFFFFF"
ohos:orientation="horizontal"
ohos:remote="true">
<DirectionalLayout
ohos:height="match_parent"
ohos:width="match_parent"
ohos:weight="1"
ohos:orientation="vertical">
<DirectionalLayout
ohos:height="match_parent"
ohos:width="match_parent"
ohos:weight="1"
ohos:orientation="horizontal">
<DirectionalLayout
ohos:height="match_parent"
ohos:width="match_parent"
ohos:weight="1"
ohos:orientation="vertical"
ohos:alignment="center">
<Text
ohos:height="match_content"
ohos:width="match_content"
ohos:text="消耗热量"
ohos:top_margin="5vp"
ohos:text_size="10fp"
ohos:text_weight="500">
</Text>
<Text
ohos:height="60vp"
ohos:width="60vp"
ohos:text="2.1 Kcal"
ohos:text_alignment="center"
ohos:text_color="#1281f0"
ohos:background_element="$media:aa_1"
ohos:text_size="10fp"
ohos:text_weight="500"/>
</DirectionalLayout>
<DirectionalLayout
ohos:height="match_parent"
ohos:width="match_parent"
ohos:weight="1"
ohos:orientation="vertical"
ohos:alignment="center">
<Text
ohos:height="match_content"
ohos:width="match_content"
ohos:text="实时心率"
ohos:top_margin="5vp"
ohos:text_size="10fp"
ohos:text_weight="500">
</Text>
<Text
ohos:height="60vp"
ohos:width="60vp"
ohos:text="99/min"
ohos:text_alignment="center"
ohos:text_color="#1281f0"
ohos:background_element="$media:aa_1"
ohos:text_size="10fp"
ohos:text_weight="500"/>
</DirectionalLayout>
</DirectionalLayout>
<DirectionalLayout
ohos:height="60vp"
ohos:width="match_parent"
ohos:align_parent_bottom="true"
ohos:background_element="#1281f0"
ohos:alignment="center"
ohos:orientation="vertical">
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_parent"
ohos:alignment="center"
ohos:orientation="horizontal">
<Text
ohos:height="match_content"
ohos:width="match_content"
ohos:text="$string:cardwidget_title"
ohos:text_color="#E5FFFFFF"
ohos:text_size="16fp"
ohos:text_weight="600"/>
<Text
ohos:height="match_content"
ohos:width="match_content"
ohos:text="3350"
ohos:text_color="#E5FFFFFF"
ohos:text_size="16fp"
ohos:text_weight="1000"/>
</DirectionalLayout>
<Text
ohos:height="match_content"
ohos:width="match_content"
ohos:text="$string:cardwidget_introduction"
ohos:text_color="#ffffff"
ohos:text_size="14fp"
ohos:text_weight="800"
ohos:top_margin="7vp"/>
</DirectionalLayout>
</DirectionalLayout>
<DirectionalLayout
ohos:height="match_parent"
ohos:width="match_parent"
ohos:weight="1">
</DirectionalLayout>
</DirectionalLayout>
点击图标上划,出现卡片。
点击右上角图钉按钮,将卡片放在屏幕中。
长按应用,出现服务卡片。
点击服务卡片选择界面,上下滑动可选择卡片内容。
点击添加到桌面,则将卡片添加到桌面中。
至此,卡片服务应用就以全部开发完成,后续会对卡片内部进行相关编写。使其进行动态的刷新及动态的获取数据。