Custom native tags
You can define custom native tags to extend the built-in tag set of dynamic cards with platform-specific UI components on Android, iOS, and HarmonyOS.
Implement custom tags on the client
Android
Inherit CCardWidget and implement its abstract methods. The primary method is onCreateView, which returns the custom View.
public class CustomCubeWidget extends CCardWidget {
private static final String TAG = "CustomCubeWidget";
private Map<String, Object> createMap;
private TextView root;
public CustomCubeWidget(Context context) {
super(context);
}
@Override
public View onCreateView(Map<String, Object> params, int width, int height) {
MPLogger.debug(TAG, "onCreateView:" + width + "," + height);
createMap = params;
for (String key : params.keySet()) {
MPLogger.debug(TAG, key + ":" + params.get(key));
}
if (root == null) {
root = new TextView(getContext());
setText(root, params.get("value") + "");
root.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Map<String, Object> eventParams = new HashMap<>();
eventParams.put("p1", "widget:" + System.currentTimeMillis());
CubeService.instance().getEngine().sendEvent(createMap, "on-WidgetToCube", eventParams);
}
});
}
return root;
}
@Override
public void onReuse(Map<String, Object> params, int width, int height) {
MPLogger.debug(TAG, "onReuse");
}
@Override
public void onUpdateData(Map<String, Object> params) {
MPLogger.debug(TAG, "onUpdateData");
}
@Override
public void onRecycleAndCached() {
MPLogger.debug(TAG, "onRecycleAndCached");
}
@Override
public boolean canReuse() {
MPLogger.debug(TAG, "canReuse");
return false;
}
@Override
public void onDestroy() {
MPLogger.debug(TAG, "onDestroy");
}
@JsMethod
public void cubeToWidget(JSONObject param, CubeJSCallback callback) {
if (root != null) {
setText(root, "Received a custom method call from the card. Parameter: " + param.toJSONString());
}
callback.invoke("cubeToWidget callback data: " + System.currentTimeMillis());
}
private void setText(TextView tv, String msg) {
tv.setText("I am a native component. Click me to notify the card of the event callback, " + msg);
}
}
iOS
Inherit CCardWidget and implement its protocol methods. The onCreateView and updateData methods are required. Other methods are optional.
#import "CustomCardView.h"
@interface CustomCardView ()
@property (nonatomic, strong) UILabel *titleLabel;
@end
@implementation CustomCardView
// Required
/**
* Creates the UI. This represents the view UI of the component to be displayed.
*
* @param data A map containing data declared by the component on the card. This includes styles, events, and properties. The corresponding keys are DATA_KEY_STYLES, DATA_KEY_EVENTS, and DATA_KEY_ATTRS.
* @param size The size (width and height) of the view.
* @return An active UIView.
*/
- (UIView *)onCreateView:(NSDictionary *)data size:(CGSize)size {
NSLog(@"Print data:%@", data);
UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
customView.backgroundColor = [UIColor redColor];
self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, size.width, 30)];
// The data source is the provided data.
self.titleLabel.text = @"A";
[customView addSubview:self.titleLabel];
return customView;
}
// Required
/**
* Updates the component data.
* @param data
*/
- (void)onUpdateData:(NSDictionary *)data {
NSLog(@"Print data:%@", data);
}
/**
* Prepares data for an existing control retrieved from the reuse pool before it is used.
* @param data Initialization data.
* @param size The size of the component when it is reused.
*/
- (void)onReuse:(NSDictionary *)data size:(CGSize)size {
NSLog(@"Print data:%@", data);
NSLog(@"Print size:%@", size);
// The data source is the provided data.
self.titleLabel.text = @"B";
}
/**
* Specifies whether the component can be reused.
* To improve efficiency, extension components can support reuse. For example, if a custom tag component is removed from the current view due to a data update, the component is placed in a reuse pool if it supports reuse. The next time the component is displayed, it is retrieved directly from the reuse pool.
* @return YES: Reuse. NO: Do not reuse.
*/
- (BOOL)canReuse {
return YES;
}
/**
* Cleans up a reusable component. If the component supports reuse (canReuse returns true), the onRecycleAndCached method is called after the component enters the reuse pool. This indicates that the component is off-screen and in the cache, and its resources need to be cleared.
*/
- (void)onRecycleAndCache {
// Clear the SubView content on the custom view.
self.titleLabel.text = @"";
}
HarmonyOS
Create a custom component by calling Component
@Component
export struct MyCustomComponent {
@State
node: CKNode = new CKNode();
@State
cardInstance: CubeCard | null = null;
@State
text: string = ""
aboutToAppear(): void {
if (this.node) {
this.node.component = this;
this.updateWidgetData()
this.node.onChange = (type: NodeChangeType, changeNode: CKNode, childNode: CKNode | null) => {
this.updateWidgetData()
};
}
}
build() {
if (this.node) {
Column() {
Text("I am a native component. Click me to notify the card of the event callback, "+this.text).onClick((event) => {
let args: Record<string, object> = {}
args["p1"] = "widget:" + systemDateTime.getTime() as ESObject
CWidgetApi.getInstance().sendEventToJS(this.node, 'on-WidgetToCube', args)
})
}
.width(this.node.size.width)
.height(this.node.size.height)
.position(this.node.position)
}
}
updateWidgetData(): void {
this.text = this.node.attributes.get("value") as string
}
public cubeToWidget(instance: FalconInstance, args: object, func: IFalconCallback): void {
this.text = "Received a custom method call from the card. Parameter: "+JSON.stringify(args)
func.invoke("cubeToWidget callback data: " + systemDateTime.getTime())
}
}
Create a ComponentBuilder
@Builder
export function buildMyCustomComponent(builder: ComponentBuilder) {
MyCustomComponent({
node: builder.node,
cardInstance: builder.cardInstance
})
}
Register custom tags on the client
Android
The first parameter is the custom tag name. On the card, this tag is enclosed in angle brackets (<>). Add a prefix to prevent conflicts with built-in dynamic card tags. The second parameter is the fully qualified class name of the custom tag implementation. Make sure this class is not obfuscated by ProGuard.
Collection<CubeWidgetInfo> widgetInfos = new LinkedList<>();
widgetInfos.add(new CubeWidgetInfo("custom-widget", CustomCubeWidget.class.getName()));
CubeService.instance().getEngine().registerWidgets(widgetInfos);
iOS
The first parameter is the custom tag name, which must match the tag used in the card. On the card, this tag is enclosed in angle brackets (<>). Add a prefix to prevent conflicts with built-in dynamic card tags. The second parameter is the class name of the implementation class.
CubeWidgetInfo *widgetInfo = [CubeWidgetInfo new];
widgetInfo.tag = @"custom-widget";
widgetInfo.className = [CustomCardView class];
NSMutableArray *widgetArray = [NSMutableArray array];
[widgetArray addObject:widgetInfo];
[[[CubeService sharedInstance] getEngine] registerWidgets:[NSArray arrayWithArray:widgetArray]];
HarmonyOS
The first parameter of CWidgetInfo is the custom tag name. On the card, this tag is enclosed in angle brackets (<>). Add a prefix to prevent conflicts with built-in dynamic card tags. The second parameter is the WrappedBuilder instance.
let customBuilder: WrappedBuilder<[ComponentBuilder]> = wrapBuilder(buildMyCustomComponent)
let customWidgetInfo: CWidgetInfo = new CWidgetInfo("custom-widget", customBuilder)
CubeEngine.getInstance().registerWidgets([customWidgetInfo])
Call custom tags from the card
Use the tag name you defined to reference the custom component. In this example, the tag is custom-widget.
<template>
<div class="root">
<text class="message" :value="message" @click="onClick()"></text>
<custom-widget ref="widget" class="custom" :value="message2" @on-WidgetToCube="clientToCube(param)">
</custom-widget>
</div>
</template>
<script>
export default {
data: {
message : 'This is the text of the card. Click me to call the custom component.',
message2 : 'This is the value passed to the custom component.'
},
beforeCreate() {
this.message = 'This is the text of the card. Click me to call the custom component.'
this.message2 = 'This is the value passed to the custom component.'
},
didAppear() {
},
methods: {
onClick() {
console.info('invoke on-click event');
this.$refs.widget.cubeToWidget({
'auto':"This is the parameter sent to the custom component's method."
},ret=>{
this.message = "Received the callback from the custom component's method. Parameters: "+JSON.stringify(ret);
});
},
// Handles the event callback from the custom component.
clientToCube(param){
console.info(param.p1)
this.message2 = "Received the event callback from the custom component. Parameters: "+JSON.stringify(param)
}
}
}
</script>
<style>
.root {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: white;
width: 100%;
height: 800rpx;
}
.custom {
color: black;
font-size: 20rpx;
width: 100%;
height: 600rpx;
}
.message {
color: black;
font-size: 20rpx;
width: 100%;
height: 200rpx;
}
</style>
Methods for calling custom tags from the card
Card call
this.$refs.widget.cubeToWidget({
'auto':"I am the parameter sent to the custom component method"
},ret=>{
this.message = "Received the callback from the custom component method. Parameter: "+JSON.stringify(ret);
});
Client reception
Android
@JsMethod
public void cubeToWidget(JSONObject param, CubeJSCallback callback) {}
iOS
// Configure the method based on convention.
CK_EXPORT_METHOD(@selector(cubeToWidget:callBack:))
// Custom tag calls a native method.
- (void)cubeToWidget:(NSDictionary *)params callBack:(CubeModuleMethodCallback)callback {
}
HarmonyOS
public cubeToWidget(instance: FalconInstance, args: object, func: IFalconCallback): void {}
Client-side callbacks for notification cards
Client notification
Android
CubeService.instance().getEngine().sendEvent(Map<String, Object> componentData, String eventName, @Nullable Map<String, Object> eventParams)
iOS
/**
* self.data: The data from the onCreateView method.
* eventName: The method name on the card, for example, @"on-WidgetToCube".
* eventParams: The parameters passed to the card, for example, @{@"p1":@"Custom tag calls a cube method"}.
*/
[[[CubeService sharedInstance] getEngine]
sendEvent:self.data
eventName:eventName
eventParams:eventParams];
HarmonyOS
CWidgetApi.getInstance().sendEventToJS(node: CKNode, methodName: string, args: Record<string, Object> | null)
Card receives notification
The card listens for client notifications by binding @eventName to a handler, as shown in the following example.
<custom-widget ref="widget" class="custom" :value="message2" @on-WidgetToCube="clientToCube(param)">