[this article is participating in the "prize essay | harmony OS essay contest"]
Service card overview
The service card (hereinafter referred to as "card") is FA It is an interface display form of FA, which puts the important information or operations of FA in front of the card, so as to achieve the purpose of direct service and reduce the experience level.
Cards are often embedded in other applications (currently only system applications are supported) and displayed as part of their interfaces. They also support basic interactive functions such as pulling up pages and sending messages. The card user is responsible for displaying cards.
FA An example is shown in the figure below.
Basic concepts
- Card provider
Provide HarmonyOS application or atomization service for card display content, and control card display content, control layout and control click events.
- Card user
The host application that displays the card content controls the display position of the card in the host.
- Card management service
It is used to manage the resident agent service of cards added in the system, including the management and use of card objects, periodic refresh of cards, etc.
Operation mechanism
The card management service includes the following modules:
- Periodic refresh: after a card is added, a scheduled task is started according to the card refresh policy to periodically trigger the card refresh.
- Card cache management: after the card is added to the card management service, the view information of the card is cached, so that the cached data can be returned directly when the card is obtained next time to reduce the delay.
- Card life cycle management: pause the refresh of cards when they are switched to the background or blocked; And update and clean up card data in the card upgrade / uninstall scenario.
- Card user object management: manages the RPC object of the card user, which is used for verification of the user's request and callback processing after card update.
- Communication adaptation layer: responsible for RPC communication with card users and providers.
The card provider includes the following modules:
- Card service: it is implemented by the developer of the card provider. The developer implements onCreateForm, onUpdateForm and onDeleteForm, processes requests for creating, updating and deleting cards, and provides corresponding card services.
- Card provider instance management module: implemented by the card provider developer, it is responsible for persistent management of card instances allocated by the card management service.
- Communication adaptation layer: provided by the HarmonyOS SDK, it is responsible for communicating with the card management service and is used to actively push the card update data to the card management service.
1. Basic steps
- Open DevEco Studio software and create a new project
- Create mobile applications using java programming language
- Fill in project information
- Show project directory list
2. Create service card
- Hongmeng system development provides many card styles for developers to choose
- Select grid card style here
- Fill in the setting card information
- Here, I select 2 * 2 and 2 * 4 cards. After the card is created, click config The configuration information of the card will be sent in the JSON project configuration file
- In the layout folder, you can see the selected card layout file automatically created by the system. Developers can directly design their own card effects on the layout
3. 2 * 2 music card design
- The corresponding layout codes are as follows
<?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="#66000000" ohos:orientation="vertical" ohos:remote="true"> <DirectionalLayout ohos:height="match_content" ohos:width="match_parent" ohos:alignment="vertical_center" ohos:orientation="horizontal" ohos:weight="1"> <Image ohos:height="60vp" ohos:width="60vp" ohos:image_src="$media:campuslife_bg" ohos:scale_mode="zoom_start" ohos:start_margin="12vp" ohos:top_margin="12vp"/> <DirectionalLayout ohos:height="60vp" ohos:margin="5fp" ohos:width="match_parent" ohos:alignment="center" ohos:orientation="vertical"> <Text ohos:height="match_content" ohos:width="match_parent" ohos:text="Song title" ohos:text_color="#E5FFFFFF" ohos:text_size="19fp" ohos:text_weight="900" ohos:truncation_mode="ellipsis_at_end"/> <Text ohos:height="match_content" ohos:width="match_parent" ohos:text="Singer name" ohos:text_color="#aaFFFFFF" ohos:text_size="15fp" ohos:text_weight="600" ohos:top_margin="2vp" ohos:truncation_mode="ellipsis_at_end"/> </DirectionalLayout> </DirectionalLayout> <DirectionalLayout ohos:height="match_content" ohos:width="match_parent" ohos:alignment="bottom" ohos:margin="12vp" ohos:orientation="horizontal" ohos:weight="1"> <Image ohos:height="30vp" ohos:width="30vp" ohos:image_src="$media:button_playmode_shuffle" ohos:scale_mode="stretch" ohos:weight="1"/> <Image ohos:height="30vp" ohos:width="30vp" ohos:image_src="$media:button_previous" ohos:scale_mode="stretch" ohos:weight="1"/> <Image ohos:height="30vp" ohos:width="30vp" ohos:image_src="$media:button_play" ohos:scale_mode="stretch" ohos:weight="1"/> <Image ohos:height="30vp" ohos:width="30vp" ohos:image_src="$media:button_next" ohos:scale_mode="stretch" ohos:weight="1"/> <Image ohos:height="30vp" ohos:width="30vp" ohos:image_src="$media:button_favorite" ohos:scale_mode="stretch" ohos:weight="1"/> </DirectionalLayout> </DirectionalLayout>
4. 2 * 4 sports travel card
- The xml layout code of its 2 * 4 card is as follows:
<?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="#aaffffff" ohos:margin="10fp" ohos:orientation="vertical" ohos:remote="true"> <DirectionalLayout ohos:height="match_content" ohos:width="match_parent" ohos:alignment="center" ohos:orientation="horizontal" ohos:weight="1"> <Text ohos:height="match_content" ohos:width="match_content" ohos:background_element="$graphic:background_facard_text" ohos:text="delicious food" ohos:padding="5fp" ohos:margin="8fp" ohos:text_alignment="center" ohos:text_size="12fp" ohos:weight="1"/> <Text ohos:height="match_content" ohos:width="match_content" ohos:background_element="$graphic:background_facard_text" ohos:padding="5fp" ohos:margin="8fp" ohos:text="hotel" ohos:text_alignment="center" ohos:text_size="12fp" ohos:weight="1"/> <Text ohos:height="match_content" ohos:width="match_content" ohos:background_element="$graphic:background_facard_text" ohos:text="scenic spot" ohos:padding="5fp" ohos:margin="8fp" ohos:text_alignment="center" ohos:text_size="12fp" ohos:weight="1"/> <Text ohos:height="match_content" ohos:width="match_content" ohos:background_element="$graphic:background_facard_text" ohos:text="gas station" ohos:padding="5fp" ohos:margin="8fp" ohos:text_alignment="center" ohos:text_size="12fp" ohos:weight="1"/> <Text ohos:height="match_content" ohos:width="match_content" ohos:background_element="$graphic:background_facard_text" ohos:text="supermarket" ohos:padding="5fp" ohos:margin="8fp" ohos:text_alignment="center" ohos:text_size="12fp" ohos:weight="1"/> </DirectionalLayout> <DirectionalLayout ohos:height="match_content" ohos:width="match_parent" ohos:alignment="center" ohos:orientation="horizontal" ohos:weight="1"> <DirectionalLayout ohos:height="match_parent" ohos:width="match_content" ohos:alignment="center" ohos:orientation="vertical" ohos:weight="1"> <Image ohos:height="40vp" ohos:width="40vp" ohos:image_src="$media:ic_sport_hauxue2"/> <Text ohos:height="match_content" ohos:width="match_content" ohos:text="skiing" ohos:text_size="14fp"/> </DirectionalLayout> <DirectionalLayout ohos:height="match_parent" ohos:width="match_content" ohos:alignment="center" ohos:orientation="vertical" ohos:weight="1"> <Image ohos:height="40vp" ohos:width="40vp" ohos:image_src="$media:ic_sport_jianshen2"/> <Text ohos:height="match_content" ohos:width="match_content" ohos:text="Bodybuilding" ohos:text_size="14fp"/> </DirectionalLayout> <DirectionalLayout ohos:height="match_parent" ohos:width="match_content" ohos:alignment="center" ohos:orientation="vertical" ohos:weight="1"> <Image ohos:height="40vp" ohos:width="40vp" ohos:image_src="$media:ic_sport_panyan2"/> <Text ohos:height="match_content" ohos:width="match_content" ohos:text="Rock Climbing" ohos:text_size="14fp"/> </DirectionalLayout> <DirectionalLayout ohos:height="match_parent" ohos:width="match_content" ohos:alignment="center" ohos:orientation="vertical" ohos:weight="1"> <Image ohos:height="40vp" ohos:width="40vp" ohos:image_src="$media:ic_sport_youyong2"/> <Text ohos:height="match_content" ohos:width="match_content" ohos:text="Swimming" ohos:text_size="14fp"/> </DirectionalLayout> <DirectionalLayout ohos:height="match_parent" ohos:width="match_content" ohos:alignment="center" ohos:orientation="vertical" ohos:weight="1"> <Image ohos:height="40vp" ohos:width="40vp" ohos:image_src="$media:ic_sport_yujia2"/> <Text ohos:height="match_content" ohos:width="match_content" ohos:text="yoga" ohos:text_size="14fp"/> </DirectionalLayout> </DirectionalLayout> </DirectionalLayout>
5. Modify application startup card code
We know from Hongmeng foundation that the MainAbility interface needs to be specified for the start of the project. We need to modify the MainAbility interface, specify the card displayed by default, and load multiple service cards designed by us
Its MainAbility code is as follows
package com.example.facardproject2; import com.example.facardproject2.slice.MainAbilitySlice; import com.example.facardproject2.widget.FormController; import com.example.facardproject2.widget.FormControllerManager; import ohos.aafwk.ability.Ability; import ohos.aafwk.content.Intent; import ohos.aafwk.ability.AbilitySlice; import ohos.aafwk.ability.ProviderFormInfo; import ohos.hiviewdfx.HiLog; import ohos.hiviewdfx.HiLogLabel; public class MainAbility extends Ability { public static final int DEFAULT_DIMENSION_2X4 = 2; public static final int DIMENSION_2X2 = 1; public static final int DIMENSION_4X4 = 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); } //Create card information and return card content @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_2X4); HiLog.info(TAG, "onCreateForm: formId=" + formId + ",formName=" + formName); FormControllerManager formControllerManager = FormControllerManager.getInstance(this); FormController formController = formControllerManager.getController(formId); //Bind the card layout to the card information by calling the layout 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(); } //Update card information @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); } //Delete card @Override protected void onDeleteForm(long formId) { HiLog.info(TAG, "onDeleteForm: formId=" + formId); super.onDeleteForm(formId); FormControllerManager formControllerManager = FormControllerManager.getInstance(this); formControllerManager.deleteFormController(formId); } //Card gesture trigger @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; } //Jump to the corresponding card interface 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(); } }
6. Create service card control code
In the package of the project, create a new widget package name, and create a service card control manager class, a service card control abstract class and a service card implementation class
Create card controller is used to create, load, update and delete cards
package com.example.facardproject2.widget; import ohos.aafwk.ability.AbilitySlice; import ohos.aafwk.ability.ProviderFormInfo; import ohos.aafwk.content.Intent; import ohos.app.Context; 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; } /** * Create card information provider */ public abstract ProviderFormInfo bindFormData(); /** * Update card information */ public abstract void updateFormData(long formId, Object... vars); /** * Called when a service widget message event is received */ public abstract void onTriggerFormEvent(long formId, String message); /** * Get the destination interface of the route */ public abstract Class<? extends AbilitySlice> getRoutePageSlice(Intent intent); }
Create a card implementation class, which is used to load card objects according to the actually created card style
package com.example.facardproject2.widget; import com.example.facardproject2.ResourceTable; 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 CardWidgetImpl extends FormController { public static final int DIMENSION_2X2 = 1; public static final int DIMENSION_4X4 = 3; private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, CardWidgetImpl.class.getName()); private static final int DEFAULT_DIMENSION_2X4 = 2; private static final Map<Integer, Integer> RESOURCE_ID_MAP = new HashMap<>(); static { RESOURCE_ID_MAP.put(DIMENSION_2X2, ResourceTable.Layout_form_grid_pattern_service_widget1_2_2); RESOURCE_ID_MAP.put(DEFAULT_DIMENSION_2X4, ResourceTable.Layout_form_grid_pattern_service_widget1_2_4); RESOURCE_ID_MAP.put(DIMENSION_4X4, ResourceTable.Layout_form_grid_pattern_service_widget1_4_4); } public CardWidgetImpl(Context context, String formName, Integer dimension) { super(context, formName, dimension); } //After the card service is created, the card is displayed in the interface @Override public ProviderFormInfo bindFormData() { HiLog.info(TAG, "bind form data when create form"); return new ProviderFormInfo(RESOURCE_ID_MAP.get(dimension), context); } //Update card information @Override public void updateFormData(long formId, Object... vars) { HiLog.info(TAG, "update form data timing, default 30 minutes"); } //Gesture triggering method for content in card @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; } }
Create a card control manager class to call the card display effect selected by the user and trigger component events
package com.example.facardproject2.widget; 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.*; 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, com.example.facardproject2.widget.FormController> controllerHashMap = new HashMap<>(); private final Context context; private final Preferences preferences; /** * Initialize constructor * * @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 instantiation * * @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; } /** * The incoming card information is encapsulated by the constructor and returned to the card service * * @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 card 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 card IDs from the array * * @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 card service * * @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"; } }
7. Running steps
Select tools - > HVD manager to start Huawei simulator
The login Huawei account console pops up
Jump to the website, enter Huawei's official website and log in to Huawei's account
After successful login, DevEco Studio will show the remote simulators of different devices that can be used
Here I choose P40 simulator
8. Operation effect
After running, one more application will be found on the simulator
Long press the application icon to open the service card option
In the service card, you can find "music card" and "sports travel card"
You can set your favorite card as the default card and set it as the default card, which can be displayed by clicking the application icon
Click the top right corner to add the card to the desktop as a shortcut to enter the application
This article ends here. I hope it can help you learn the function of service card.
For code cases, please Download here
[this article is participating in the "prize essay | harmony OS essay contest"]