鸿蒙卡片实战:把外卖进度钉在桌面,一行代码一个坑
最近搞了个鸿蒙的元服务卡片,目标很简单:让用户不用点开App,在桌面上就能一眼看清外卖到哪了,还能一键联系骑手。 官方概念吹得再响,不如一行代码实在。开整!
一、卡片是啥?桌面上的“信息小窗口”
想象你手机桌面上有个固定的小区域(卡片),它就干一件事:实时显示你当前外卖订单最关键的信息,比如:
-
状态
: 商家已接单 / 骑手取餐中 / 配送中 (离你500米) / 已送达 -
预计时间
: 12:15送达 -
简单操作
: 一个大大的【联系骑手】按钮
用户点一下卡片,可能直接跳转到App里更详细的页面(或者直接打电话),但核心信息在桌面就搞定了。这就是元服务卡片的价值——服务直达桌面。
二、开撸代码:核心步骤拆解
鸿蒙卡片开发主要在 FA模型
或 Stage模型
下搞,这里以FA模型为例(概念相对简单点)。核心是几个文件:
-
Java
逻辑代码 (比如MyCardAbilitySlice.java
): 处理卡片的数据加载、更新和交互。 -
resources/base/layout
下的xml
布局文件 (比如widget_card.xml
): 定义卡片长啥样。 -
resources/base/profile
下的form_config.json
: 声明卡片支持的尺寸、类型等。
卡片布局 (widget_card.xml)
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:width="match_parent"
ohos:height="match_parent"
ohos:orientation="vertical"
ohos:padding="8vp"> <!-- 简单内边距 -->
<!-- 状态行:文字 + 可能的小图标 -->
<Text
ohos:id="$+id:tvStatus"
ohos:width="match_content"
ohos:height="match_content"
ohos:text="状态:骑手取餐中"
ohos:text_size="16fp"
ohos:text_color="#FF333333" />
<!-- 预计送达时间 -->
<Text
ohos:id="$+id:tvEta"
ohos:width="match_content"
ohos:height="match_content"
ohos:text="预计送达:12:15"
ohos:layout_margin_top="4vp"
ohos:text_size="14fp"
ohos:text_color="#666666" />
<!-- 关键!动态距离/位置信息 -->
<Text
ohos:id="$+id:tvDistance"
ohos:width="match_content"
ohos:height="match_content"
ohos:text="骑手距离:约500米"
ohos:layout_margin_top="4vp"
ohos:text_size="14fp"
ohos:text_color="#2196F3" /> <!-- 用个醒目的蓝色 -->
<!-- 大大的联系骑手按钮 -->
<Button
ohos:id="$+id:btnCallRider"
ohos:width="match_parent"
ohos:height="35vp"
ohos:text="联系骑手"
ohos:background_element="#FF4081" <!-- 粉色按钮醒目 -->
ohos:text_color="#FFFFFF"
ohos:layout_margin_top="12vp"
ohos:clickable="true" /> <!-- 记住要设置可点击! -->
</DirectionalLayout>
说明: 这就是个简单的垂直布局,放了几个文本和一个按钮。注意给每个需要动态更新的控件设置好 id
,后面代码里要用。
卡片逻辑 (MyCardAbilitySlice.java) - 处理数据和更新
public class MyCardAbilitySlice extends FormAbilitySlice {
// 定义卡片的ID
private static final int CARD_ID = 100; // 随便写个唯一ID
@Override
protected void onStart(Intent intent) {
super.onStart(intent);
// 1. 设置布局
setUIContent(ResourceTable.Layout_widget_card);
// 2. 找到布局里的控件
Text tvStatus = (Text) findComponentById(ResourceTable.Id_tvStatus);
Text tvEta = (Text) findComponentById(ResourceTable.Id_tvEta);
Text tvDistance = (Text) findComponentById(ResourceTable.Id_tvDistance);
Button btnCallRider = (Button) findComponentById(ResourceTable.Id_btnCallRider);
// 3. 【模拟】获取外卖数据!真实项目这里是从网络或本地数据库取
// 假设我们有个方法 fetchLatestDeliveryData() 返回一个对象
DeliveryData deliveryData = fetchLatestDeliveryData();
// 4. 把数据塞到控件上显示
tvStatus.setText("状态:" + deliveryData.getStatus());
tvEta.setText("预计送达:" + deliveryData.getFormattedEta());
tvDistance.setText("骑手距离:" + deliveryData.getDistance() + "米");
// 5. 处理按钮点击 - 联系骑手
btnCallRider.setClickedListener(component -> {
// 真实场景:唤起电话拨号盘,拨打骑手电话
// 这里简单用Toast模拟
getUITaskDispatcher().asyncDispatch(() -> {
new ToastDialog(getContext())
.setText("正在呼叫骑手: " + deliveryData.getRiderPhone())
.show();
});
// 实际代码可能是:
// Intent callIntent = new Intent(Intent.ACTION_DIAL);
// callIntent.setUri(Uri.parse("tel:" + deliveryData.getRiderPhone()));
// startAbility(callIntent);
});
// 6. 【关键】定时更新!卡片需要自己刷新数据
// 这里用个简单定时器模拟,真实项目用系统提供的更新机制或Push
getUITaskDispatcher().delayDispatch(this::updateCardData, 30000); // 30秒更新一次
}
// 模拟获取外卖数据的方法
private DeliveryData fetchLatestDeliveryData() {
// 这里应该是网络请求或数据库查询...
// 返回一个假数据
DeliveryData data = new DeliveryData();
data.setStatus("配送中");
data.setEta(System.currentTimeMillis() + 1800000); // 半小时后
data.setDistance(500); // 500米
data.setRiderPhone("13800138000");
return data;
}
// 更新卡片数据的方法 (逻辑和上面类似,需要重新获取数据并更新UI)
private void updateCardData() {
DeliveryData newData = fetchLatestDeliveryData();
// ... 更新UI控件显示 newData ...
// 再次设置定时更新
getUITaskDispatcher().delayDispatch(this::updateCardData, 30000);
}
// ... 其他生命周期方法,比如onInactive, onActive等,根据需要处理卡片可见性变化 ...
}
// 简单的外卖数据Bean
class DeliveryData {
private String status;
private long eta; // 预计送达时间戳
private int distance; // 距离(米)
private String riderPhone;
// getters and setters...
public String getFormattedEta() {
// 把时间戳转换成 "HH:mm" 格式
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
return sdf.format(new Date(eta));
}
}
这里有几个着重解释下 :
-
找控件:
findComponentById
用之前XML里定义的id找到对应的UI组件。 -
数据获取:
fetchLatestDeliveryData()
是模拟的!真实项目这里最复杂:可能需要调用网络API,访问本地数据库,或者接收来自App主模块的事件通知。卡片本身是相对独立的。 -
数据绑定: 把获取到的数据设置到对应的Text、Button等控件上。
-
按钮事件:
setClickedListener
处理用户点击“联系骑手”。这里模拟了Toast,真实场景是调用系统电话能力 (ACTION_DIAL
)。 -
定时更新: 这是卡片能“动”起来的关键!这里用了简单的
delayDispatch
模拟定时循环(30秒一次)。生产环境强烈建议:-
使用
updateForm()
方法结合系统提供的更新机制。 -
利用Push推送(比如通过华为推送服务)在订单状态变化时实时触发卡片更新,体验更好。
-
-
FormAbilitySlice
: 这是开发卡片的核心类,继承它。
卡片配置 (form_config.json)
{
"forms": [
{
"name": "DeliveryTrackerCard", // 你卡片的名称
"description": "外卖进度跟踪卡片", // 描述
"src": "./js/widget/pages/card/index", // Stage模型常用,FA模型主要靠上面的Java
"window": {
"designWidth": 720, // 设计基准宽度
"autoDesignWidth": true // 是否自动适配宽度
},
"colorMode": "auto", // 颜色模式自动
"isDefault": true, // 是否是默认卡片
"updateEnabled": true, // 允许更新!重要!
"scheduledUpdateTime": "10:30", // 每天固定更新时间(可配合定时更新)
"updateDuration": 1, // 定时更新周期(单位小时),配合上面用
"defaultDimension": "2*2", // 默认支持的尺寸 2行2列
"supportDimensions": [ // 支持的其他尺寸
"2*2",
"2*4",
"4*4"
],
"type": "Java" // 卡片类型,这里是Java卡片
}
]
}
关键配置:
-
updateEnabled
: 必须为true
,否则你的定时更新或Push更新都无效! -
scheduledUpdateTime
和updateDuration
: 系统级别的定时更新策略,作为你代码内定时更新的补充或兜底。 -
supportDimensions
: 声明你的卡片支持哪些尺寸布局,你可能需要为不同尺寸提供不同的布局XML。
三、踩坑实录 & 最佳实践
-
数据从哪里来? 这是最大的坑!卡片本身资源受限。
-
理想方案: App主进程通过
FormProvider
的onUpdateForm
主动推送数据给卡片。或者用公共的DataAbility
/分布式数据
共享数据。 -
折中方案: 卡片自己调用网络API(注意权限和后台网络限制)或读本地数据库(需要和App约定好存储位置和格式)。
-
简单模拟: 像上面代码那样写死或本地模拟(仅用于演示)。
-
-
更新频率控制: 卡片刷新太频繁耗电!太慢信息不准。
-
利用系统定时更新 (
scheduledUpdateTime
+updateDuration
) 做兜底(比如每小时一次)。 -
关键状态变化(如商家接单->骑手取餐->配送中->送达)一定要用Push实时推!体验提升巨大。
-
代码内定时更新 (
delayDispatch
或Timer
) 可用于中间状态的微调(如距离变化),但间隔不宜过短(>1分钟)。
-
-
卡片尺寸适配: 用户可能选2*2或2*4。
-
在
resources/base/layout
下为不同尺寸创建不同的布局文件,如widget_card_2x2.xml
,widget_card_2x4.xml
。 -
在
form_config.json
的对应卡片配置的supportDimensions
里声明支持的尺寸。 -
在
MyCardAbilitySlice
的onStart
里,根据intent
携带的卡片尺寸信息 (ohos.extra.param.key.form_dimension
),动态加载不同的布局 (setUIContent
)。
-
-
按钮跳转: 点击按钮想打开App的某个页面?
-
使用
Intent
指定目标Ability (比如".MainAbility"
) 和可能的参数 (setParam
)。 -
调用
startAbility(intent)
。确保目标Ability的配置正确。
-
-
性能!性能!性能! 卡片资源有限。布局简单点,逻辑轻量点,网络请求优化点。别在卡片里做耗时操作!
四、效果 & 真香时刻
折腾完这些,当你在手机桌面上长按 -> 服务卡片 -> 找到你的“外卖进度卡片” -> 添加到桌面。看着它静静躺在那里,实时显示着你的外卖距离,点一下按钮就能打电话给骑手... 那种“信息随手可得”的畅快感,就是元服务卡片最大的魅力!
总结一下核心:
-
布局XML: 画个简单好看的皮囊。
-
Java逻辑: 定时/实时拿数据,塞到皮囊里,处理按钮点击。
-
配置JSON: 告诉系统你的卡片叫啥、多大、能不能更新。
-
数据通道: 搞定数据从哪里来(最难也最关键!)。
-
尺寸适配: 让它在不同大小的卡片位置都好看。
代码虽然简化了,但流程和关键点都在这了。
搞起吧,把服务直接“钉”在用户桌面上,体验提升肉眼可见!
更多推荐
所有评论(0)