一, 前言
自从HarmonyOS发布以来, 原子化服务卡片成为亮点中亮点, 我平常上班交通工具都是公交车多, 平常在出门前,下班前都会打开微信小程序[车来了精准实时公交]查看要坐的公交现在什么位置, 每次都要先打开微信, 找到小程序,才可以查看公交行程情况, 有些麻烦, 这时HarmonyOS原子化服务卡片出现了, 我就想能不能把某路公交车行程直接显示在卡片上,这样就不用每次都要先打开微信,再找到小程序查看, 有了想法当然就是运动了,虽然我拿不到公交车行程信息, 但是我可以模拟,把固定几路公交车信息录入数据库,还有某路公交经过的站牌也录入到数据. 有了模拟数据, 一切都好办了, 我们就开始动手吧.
二, 实现效果
每个服务卡片绑定一路公交车, 设置好候车站, 每5秒更新公交车前进一个站, 当公交车到达候车站时, 公交车前进进度条停止. 除了显示公交车前进状态, 也显示出前所在位置站牌.
编辑卡片视频-不同规格卡片绑定不同的公交车线路: https://v.youku.com/v_show/id_XNTE4MjAzOTE1Ng==.html?x=&sharefrom=android&sharekey=c7aa0569d2c0d621414c23f1dccb240d0
三, 创建工程
在这当作你已经安装好最新版本DevEco-Studio开发工具, 点击File -> New -> New Project... 弹出Create HarmonyOS Project窗口, 这里我选择空白JS模板创建, 写界面还是JS比较方便些, 对于有一定前端知识的小伙伴来说, 创建JS项目的操作我就不截图, 那就操作两个界面.
四, 生成服务卡片
在左边树形目录entry 右击New -> Service Widget
给卡片起个靓名字, 同时选择创建哪些规格卡片,既然是原子化服务卡片, 当然是全部选择了.
点击完成, 在Java包下自动生成一个widget的包, 该包自动创建卡片相关代码, 里面使用的是工厂模式最通用的单例模式, 同时在JS目录下自动生成一个widgetBus目录, 里面包含服务卡片界面相关代码.
五, 主界面开发
虽然本贴子主要介绍是服务卡片, 但要服务卡片有丰富的内容显示和动画, 还得有后台的大力支持, 比如Data Ability保存卡片需要数据, Service Ability 动态显示公交车前进动画进度条,这里先介绍一下整个项目的结构:
主界面效果:
hml代码以下:
- <div class="container">
- <div class="search-container">
- <input id="input" class="input" type="text" value="" maxlength="20"
- headericon="/common/images/search.png" placeholder="搜索线路" onchange="change">
- </input>
- </div>
- <div class="body-container">
- <div class="item" for="{{ value in busList }}" onclick="open({{ value.id }})">
- <div class="item-count"><text>{{ value.busNum }}</text></div>
- <div class="detail-container">
- <div class="left-container">
- <div class="left-div"><text>候车站 {{ value.waitingStation }}</text></div>
- <div class="left-div"><text>开往 {{ value.endStation }}</text></div>
- </div>
- <div class="right-container">
- <div class="right-div"><text class="right-text">{{ value.timeRemaining }} 分钟</text></div>
- <div class="right-div"><text>{{ value.stationsRemaining }} 站 / {{ value.kmRemaining }} km</text></div>
- </div>
- </div>
- </div>
- </div>
- </div>
复制代码这里就只贴hml代码, JS和CSS可以到gitee上查看源代码
车站牌效果: 手机版可以左右滑动
hml代码以下:
- <div class="container">
- <div class="header-container">
- <div class="header-back" onclick="onBack">
- <image src="/common/images/left.png" />
- </div>
- <text>{{ stationDetail[0].parentName }}</text>
- </div>
- <div class="body-container">
- <div class="station-title" ><text>{{ stationDetail[0].startStation }} - {{ stationDetail[0].endStation }}</text></div>
- <list scrollbar="on" style="flex-direction: row;">
- <list-item>
- <div class="station-detail" style="width: {{totalWidth-(stationWidth-30)}}px;">
- <progress class="min-progress" type="horizontal" percent= "{{currentPercent}}" secondarypercent="{{secondaryPercent}}"></progress>
-
- <div class="detail-text" >
- <block for="{{ stationDetail }}">
- <div style="width: {{ $idx == stationDetail.length-1 ? 30 : stationWidth }}px;">
- <text>{{$idx}} {{$item.stationName}}</text>
- </div>
- </block>
- </div>
- </div>
- </list-item>
- </list>
- </div>
- </div>
复制代码
这里就只贴hml代码, JS和CSS可以到gitee上查看源代码
六, 终于到主角登场了
原子化服务卡片来了, 不同规格卡片,不同设备显示,都是同一套代码
hml代码以下:
- <div class="image_with_info_layout" onclick="routerEvent">
- <div if="{{ mini }}" class="mini_container" >
- <image src="/common/icon.png" class="mini_image"></image>
- <text class="mini_title">{{ miniTitle }}</text>
- </div>
- <div class="normal_container">
- <div class="items_container" style="margin-top : {{ imagePaddingTop }}">
- <div class="item_container" style="display-index : 4;">
- <text class="item_title">{{ itemTitle }}</text>
- <div class="item_space"></div>
- <text class="item_content">{{ itemContent }}</text>
- </div>
- <div class="item_container" style="display-index : 3;">
- <progress class="min-progress" type="horizontal" percent= "{{currentPercent}}" secondarypercent="{{secondaryPercent}}"></progress>
- <text if="{{ detailStation }}" class="item_content">{{ detailContent }}</text>
- </div>
- </div>
- <div class="title_container">
- <div class="title_sub_container" style="flex-direction: row; margin-right: 10px;">
- <div class="station-detail" style="width: 100%;">
- <div class="detail-text" >
- <block for="{{ stationDetail }}">
- <div style="flex-direction: column;" >
- <text class="detail-text-child">{{$idx}}</text>
- <text class="detail-text-child">{{$item.stationName}}</text>
- </div>
- </block>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
复制代码这里就只贴hml代码, JS和CSS可以到gitee上查看源代码
七, 实体类代码片段:
- @Entity(tableName = "form")
- public class Form extends OrmObject {
- @PrimaryKey()
- private Long formId; // 卡片Id
- private String formName; // 卡片名称
- private Integer dimension; // 卡片规格
- private Long busId; // 公交车Id
- }
复制代码- <pre>@Entity(tableName = "bus", ignoredColumns = {"checkBtn"}) //“ignoredColumns”表示该字段不需要添加到“bus”表的属性中</pre>public class Bus extends OrmObject {
- @PrimaryKey()
- private Long id; // 主键ID
- private String busNum; // 线路号 "987路"
- private String waitingStation; // 候车站 "海珠客运站"
- private String endStation; // 开往 "天安科技园"
- private int timeRemaining; // 离候车站剩下分钟 "10分钟"
- private int stationsRemaining; // 离候车站剩下几站 "5站"
- private float kmRemaining; // 离候车站剩下距离 "1.5km"
复制代码- @Entity(tableName = "station")
- public class Station extends OrmObject {
- @PrimaryKey(autoGenerate = true)<pre> private Integer sId; // 站牌Id 自动生成</pre> private String stationName; // 站牌名 "海珠客运站总站",
- private Long parentId; // 公交车Id
- private String parentName; // 公交线路
- private String startStation; // 起始站
- private String endStation; // 终点站
- private int displayOrder; // 站牌序号
- }
复制代码- @Database(entities = {Form.class, Bus.class, Station.class}, version = 1)
- public abstract class BusComesDatabase extends OrmDatabase {
- }
复制代码要使用@Entity, @PrimaryKey, @Database前, 必须在entry目录下的build.gradle文件添加红色框内容
八, 增加长按卡片编辑页面
1. 创建卡片编辑Ability(BusCardConfigAbility)
右击entry>New>Ability>Page Ability(JS)
在BusCardConfigAbility.onstart中添加setInstanceName("BusCardConfig");
- public class BusCardConfigAbility extends AceAbility {
- public static Long formId;
- @Override
- public void onStart(Intent intent) {
- // 绑定前端BusCardConfig页面
- setInstanceName("BusCardConfig");
- // 获取卡片ID并进行保存
- IntentParams params = intent.getParams();
- formId = (long) params.getParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY);
- super.onStart(intent);
- }
- @Override
- public void onStop() {
- super.onStop();
- }
- }
复制代码 2. 在config.json配置文件中增加属性:formConfigAbility
- "formConfigAbility": "ability://BusCardConfigAbility"
复制代码
九, 编辑页面开发&编辑更新卡片逻辑开发
hml代码以下:
- <div class="container">
- <div class="search-container">
- <input id="input" class="input" type="text" value="" maxlength="20" onenterkeyclick="change"
- headericon="/common/images/search.png" placeholder="搜索线路" onchange="change">
- </input>
- </div>
- <div class="item" for="{{ value in busList }}" onclick="checked({{ value.id }})">
- <image class="todo-image" src="{{value.checkBtn}}"></image>
- <div class="item-title" ><text class="item-desc">{{ value.busNum }}</text></div>
- <div class="item-detail" >
- <div><text class="item-desc">起始站: {{ value.waitingStation }}</text></div>
- <div><text class="item-desc">终点站: {{ value.endStation }}</text></div>
- </div>
- </div>
- </div>
复制代码 JS代码以下:
- import app from '@system.app';
- const BUTTON_STATE_IMAGE = ['/common/images/checkbox-blank.png', '/common/images/checkbox-circle.png'];
- export default {
- data: {
- "busList": [
- {"id": 1, "busNum": "987路", "waitingStation": "海珠客运站总站", "endStation": "天安科技园总站", "timeRemaining": 5, "stationsRemaining": 1, "kmRemaining": 1.5, "checkBtn": BUTTON_STATE_IMAGE[0]},
- {"id": 2, "busNum": "303路", "waitingStation": "太古仓路总站", "endStation": "海珠客运站总站", "timeRemaining": 20, "stationsRemaining": 3, "kmRemaining": 3.5, "checkBtn": BUTTON_STATE_IMAGE[0]},
- {"id": 3, "busNum": "288路", "waitingStation": "大夫山公园总站", "endStation": "洛溪新城总站", "timeRemaining": 20, "stationsRemaining": 3, "kmRemaining": 3.5, "checkBtn": BUTTON_STATE_IMAGE[0]},
- {"id": 4, "busNum": "129路", "waitingStation": "洛溪新城总站", "endStation": "奥林匹克花园总站", "timeRemaining": 20, "stationsRemaining": 3, "kmRemaining": 3.5, "checkBtn": BUTTON_STATE_IMAGE[0]},
- {"id": 5, "busNum": "230路", "waitingStation": "华工大总站", "endStation": "客村立交总站", "timeRemaining": 20, "stationsRemaining": 3, "kmRemaining": 3.5, "checkBtn": BUTTON_STATE_IMAGE[0]},
- {"id": 6, "busNum": "882路", "waitingStation": "客村立交总站", "endStation": "员村总站", "timeRemaining": 20, "stationsRemaining": 3, "kmRemaining": 3.5, "checkBtn": BUTTON_STATE_IMAGE[0]}
- ],
- "keyword": "",
- "busId": -1
- },
- onInit() {
- this.getBusList();
- },
- initAction: function (code, actionData) {
- var action = {};
- action.bundleName = "com.army.study";
- action.abilityName = "com.army.study.service.BusInternalAbility";
- action.messageCode = code;
- action.data = actionData;
- action.abilityType = 1;
- action.syncOption = 0;
- return action;
- },
- getBusList: async function () {
- var actionData = {"keyword": this.keyword};
- try {
- var action = this.initAction(1001, actionData);
- var result = await FeatureAbility.callAbility(action);
- console.info(" @@result = " + JSON.stringify(result));
- this.busList = JSON.parse(result);
- } catch (pluginError) {
- console.error("getBusList : Plugin Error = " + pluginError);
- }
- },
- change(e) {
- this.keyword = e.value;
- this.getBusList();
- },
- bindingCard: async function () {
- var actionData = {"busId": this.busId};
- try {
- var action = this.initAction(1003, actionData);
- var result = await FeatureAbility.callAbility(action);
- console.info(" @@result = " + result);
- if("OK" == result) {
- // 延时500毫秒关闭当前页面
- setTimeout(function(){app.terminate();},500);
- }
- } catch (pluginError) {
- console.error("bindingCard : Plugin Error = " + pluginError);
- }
- },
- checked(id) {
- let _this = this;
- _this.busList.forEach(element => {
- element.checkBtn = element.id == id ? BUTTON_STATE_IMAGE[1] : BUTTON_STATE_IMAGE[0]
- });
- this.busId = id;
- this.bindingCard();
- }
- }
复制代码 CSS代码请移步到gitee查看源码
更新卡片数据片段代码:
- // 2. 再根据公交Id获取公交行走路线站点信息
- Bus bus = DatabaseUtils.getBusById(connect, busId);
- List<Station> stationDetail = DatabaseUtils.getStationByParentId(connect, busId);
- System.out.println("stationDetail size: " + stationDetail.size());
- // 3. 封装返回卡片需要信息数据
- ZSONObject zsonObject = new ZSONObject();
- ZSONArray zsonArray = new ZSONArray();
- for (Station obj : stationDetail) {
- ZSONObject station = new ZSONObject();
- station.put("stationName",obj.getStationName());
- station.put("parentId",obj.getParentId());
- station.put("displayOrder",obj.getDisplayOrder());
- zsonArray.add(station);
- }
- zsonObject.put("stationDetail", zsonArray);
- FormBindingData formBindingData = new FormBindingData(zsonObject);
- try {
- // 更新卡片信息
- ((MainAbility)context).updateForm(formId, formBindingData);
- } catch (FormException e) {
- e.printStackTrace();
- }
复制代码效果图:
其它Ability代码文件, 都有注释, 有兴趣的小伙伴可以下载源码查看, 项目还不算完整版, 下来会慢慢更新, 源码也会同步到gitee码云
源码在这: https://gitee.com/army16_harmony/bus-comes