HarmonyOS 鸿蒙Next 万能卡片-Codelabs挑战赛 使用Java手搓一个HarmonyOS元服务万能卡片

HarmonyOS 鸿蒙Next 万能卡片-Codelabs挑战赛 使用Java手搓一个HarmonyOS元服务万能卡片

什么是服务卡片

参考官方文档说法:https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ability-service-widget-overview-0000001062607955

服务卡片(Service Widget,以下简称“卡片”)是FA(Feature Ability)的一种界面展示形式,将FA的重要信息或操作前置到卡片,以达到服务直达,减少体验层级目的。

卡片常用于嵌入到其他应用(当前只支持系统应用)中作为其界面的一部分显示,并支持拉起页面,发送消息等基础的交互功能。卡片使用方负责显示卡片。

以下是服务卡片的一些示例:

![服务卡片示例1]

![服务卡片示例2]

下载、安装DevEco Studio

开发HarmonyOS的最为趁手的兵器,自然是DevEco Studio了。DevEco Studio是专门开发HarmonyOS应用的集成开发环境。

下载地址:https://developer.harmonyos.com/cn/develop/deveco-studio

下载安装包后,直接解压,双击exe文件,就可以安装了。也可以参考我之前写的博客(https://developer.huawei.com/consumer/cn/forum/topic/0201427672244370691?fid=0101303901040230869

创建HarmonyOS应用

如何创建一个HarmonyOS应用?这个在我的之前的博客里面也有介绍(https://developer.huawei.com/consumer/cn/forum/topic/0201427689906950692?fid=0101303901040230869

大概的流程如下:

在打开DevEco Studio后,我们点击“Create HamonyOS Project”来创建一个项目。

![创建项目]

选择不同设备应用类型的模板。这里,我们选择了“Phone”以及一个空的Ability。

![选择模板]

下一步是配置项目的信息,比如项目名称、包名、位置SDK版本等。

![配置项目信息]

点击“Finish”之后,DevEco Studio就会我们创建好了整个应用,并且自动生成了工程代码。

![创建完成]

可以通过预览器来预览应用的界面。

![预览界面]

也通过远程设备模拟器来运行咱们的应用。这里需要注意,必须要注册华为开发者账号并通过开发者实名认证,此能使用远程设备模拟器。

![运行应用]

Java开发服务卡片

右键项目代码包,可以看到“Service Widget”选项。点击该选项可以创建卡片。

![创建卡片]

选择模板

![选择模板]

给卡片起个名字

![命名卡片]

此时,会自动生成一个widget的包,该包下会自动创建卡片相关的代码。

![生成代码]

FormController

FormController定义了卡片的控制器。代码如下:

package com.waylau.hmos.abilityservicewidget.widget.controller;

import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.ability.ProviderFormInfo;
import ohos.aafwk.content.Intent;
import ohos.app.Context;

/**
 * The api set for form controller.
 */
public abstract class FormController {
    /**
     * Context of ability
     */
    protected final Context context;
    /**
     * The name of current form service widget
     */
    protected final String formName;
    /**
     * The dimension of current form service widget
     */
    protected final int dimension;

    public FormController(Context context, String formName, Integer dimension) {
        this.context = context;
        this.formName = formName;
        this.dimension = dimension;
    }

    /**
     * Bind data for a form
     *
     * @return ProviderFormInfo
     */
    public abstract ProviderFormInfo bindFormData();

    /**
     * Update form data
     *
     * @param formId the id of service widget to be updated
     * @param vars   the data to update for service widget, this parameter is optional
     */
    public abstract void updateFormData(long formId, Object... vars);

    /**
     * Called when receive service widget message event
     *
     * @param formId  form id
     * @param message the message context sent by service widget message event
     */
    public abstract void onTriggerFormEvent(long formId, String message);

    /**
     * Get the destination ability slice to route
     *
     * @param intent intent of current page slice
     * @return the destination ability slice name to route
     */
    public abstract Class<? extends AbilitySlice> getRoutePageSlice(Intent intent);
}

FormControllerManager

FormControllerManager是FormController的管理者。代码如下:

package com.waylau.hmos.abilityservicewidget.widget.controller;

import ohos.app.Context;
import ohos.data.DatabaseHelper;
import ohos.data.preferences.Preferences;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.utils.zson.ZSONObject;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;

/**
 * Form controller manager.
 */
public class FormControllerManager {
    private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, FormControllerManager.class.getName());
    private static final String PACKAGE_PATH = "com.waylau.hmos.abilityservicewidget.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;

    /**
     * Constructor with context.
     *
     * @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);
    }

    /**
     * Singleton mode.
     *
     * @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;
    }

    /**
     * Save the form id and form name.
     *
     * @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;
        }
    }

    /**
     * Get the form controller instance.
     *
     * @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;
    }

    /**
     * Get all form id from the share preference
     *
     * @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;
    }

    /**
     * Delete a form controller
     *
     * @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";
    }
}

PhotoWidgetImpl

PhotoWidgetImpl是对FormController的一个默认实现。代码如下:

package com.waylau.hmos.abilityservicewidget.widget.photowidget;

import com.waylau.hmos.abilityservicewidget.ResourceTable;
import com.waylau.hmos.abilityservicewidget.widget.controller.FormController;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.ability.ProviderFormInfo;
import ohos.aafwk.content.Intent;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import java.util.HashMap;
import java.util.Map;

public class PhotoWidgetImpl extends FormController {
    private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, PhotoWidgetImpl.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(DEFAULT_DIMENSION_2X2, ResourceTable.Layout_form_image_with_information_photowidget_2_2);
    }

    public PhotoWidgetImpl(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;
    }
}

修改form_image_with_information_photowidget_2_2布局文件

form_image_with_information_photowidget_2_2文件如下。

![布局文件]

可以对其进行修改,以满足我们的需要。

比如,我们可以修改string.json文件来修改标题和描述。

![修改string.json]

也可以修改form_image_with_information_photowidget_2_2文件中Image组件,来实现自定义图片。比如,下面的狗狗图片。

![狗狗图片]

狗狗图片dog.jpg需要事先放置于media目录下。

如何使用服务卡片

运行应用后,找到我们的应用的图标,而后进行一个上滑。

![上滑应用]

此时,就能看到服务卡片显示出来啦!

![服务卡片显示]

哈哈,可爱不~

源码

可以在仓库(https://github.com/waylau/harmonyos-tutorial)的AbilityServiceWidget应用下找到源码


更多关于HarmonyOS 鸿蒙Next 万能卡片-Codelabs挑战赛 使用Java手搓一个HarmonyOS元服务万能卡片的实战教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS 鸿蒙Next 万能卡片-Codelabs挑战赛 使用Java手搓一个HarmonyOS元服务万能卡片的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS(鸿蒙)系统中,创建万能卡片(Widget)通常涉及使用ArkTS(Ark UI TypeScript)或eTS(Enhanced TypeScript)语言进行开发,而非Java。这是因为鸿蒙系统在设计时,为了提供更高效、更贴近原生体验的开发方式,采用了ArkUI框架,该框架主要支持eTS和ArkTS进行UI界面的构建。

然而,若你希望在鸿蒙系统中实现类似万能卡片的功能,并且坚持使用Java进行开发,那么你需要了解的是,鸿蒙系统虽然支持Java进行应用层开发,但在UI组件和卡片方面,Java并非首选或推荐的开发语言。

尽管如此,你仍可以尝试通过Java调用鸿蒙提供的API(如果可用)来间接实现某些卡片功能,但这通常较为复杂且可能受限。更推荐的做法是学习和使用eTS或ArkTS来开发鸿蒙应用及卡片,以充分利用鸿蒙系统的优势。

如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html

回到顶部