前言
本人是JS攻城狮,java属于初学状态,写的不好的地方还请多多指教!
为什么决定开发一个星座运势服务卡片?
主要有以下几点:
- 老年人和女性都比较关心运势,卡片类型刚好能够清晰展示运势,同时也不需要太多操作,与服务卡片理念契合(弱交互、强服务)。
- 聚合api上刚好有相应接口,可以直接调用,比较方便。
实现效果
实现了三个卡片(2X2、2X4、4X4)和一个内容页,基于api6开发(API 6目前只支持运行在Phone设备的模拟器上,其它设备暂不支持运行),如何使用API 6点击查看?
卡片使用原子化布局实现,根据卡片规格自适应。
内容页可查询关注的星座,调用接口来获取运势。
创建项目
搭建环境,下载开发工具并安装。下载和安装官方文档
打开安装好的DevEco Studio软件,创建项目。官方文档
填写创建的工程信息 官方文档
- Project Type:工程的类型。
- Service:原子化服务。原子化服务调试、运行时,在设备桌面上没有应用图标、免安装的。
- Application:传统方式需要安装的应用。
- Compatible API Version:兼容的SDK最低版本。
- Show in Service Center:是否在服务中心露出。如果Project Type为Service,则会同步创建一个2*2的服务卡片模板。
- 创建好的模板,我们主要会用到以下目录:
卡片JSUI部分
hml部分
布局
代码
- <div class="normal_container" >
- <!-- 2X2 2X4 布局-->
- <div class="container_top" @click="clickToApp" >
- <!-- 2X2 布局-->
- <div class="top_left">
- <div class="left_item">
- <text class="left_item_text conName">
- {{ conName }}
- </text>
- </div>
- <div class="left_item">
- <text class="left_item_text conDate">
- {{ conDate }}
- </text>
- </div>
- <div class="conAll left_item">
- <div class="s_score">
- <text class="left_item_text label_2X2">
- 综合
- </text>
- <image class="s_star_2X2" for="{{ conAllStarY }}" src="/common/starY.png"></image>
- <image class="s_star" for="{{ conAllStarN }}" src="/common/starN.png"></image>
- </div>
- </div>
- <div class="conPari left_item">
- <text class="left_item_text" >
- 速配星座
- </text>
- <text class="left_item_text conPari_text">
- {{ conPari }}
- </text>
- </div>
- </div>
- <!-- 2X4 布局-->
- <div class="top_right">
- <image src="{{ imgSrc }}" class="con_image" ></image>
- </div>
- </div>
- <!-- 4X4 布局半部分-->
- <div class="container_bottom" @click="clickToApp" >
- <div class="list_item item_line_top" >
- <div class="s_score">
- <text class="label">
- 爱情
- </text>
- <image class="s_star" for="{{ conLoveStarY }}" src="/common/starY.png"></image>
- <image class="s_star" for="{{ conLoveStarN }}" src="/common/starN.png"></image>
- </div>
- <div class="s_score" style="margin-left: 40px;" >
- <text class="label">
- 理财
- </text>
- <image class="s_star" for="{{ conMoneyStarY }}" src="/common/starY.png"></image>
- <image class="s_star" for="{{ conMoneyStarN }}" src="/common/starN.png"></image>
- </div>
- </div>
- <div class="list_item">
- <div class="s_score">
- <text class="label">
- 工作
- </text>
- <image class="s_star" for="{{ conWorkStarY }}" src="/common/starY.png"></image>
- <image class="s_star" for="{{ conWorkStarN }}" src="/common/starN.png"></image>
- </div>
- <div class="s_score" style="margin-left: 40px;" >
- <text class="label">
- 健康
- </text>
- <image class="s_star" for="{{ conHealthStarY }}" src="/common/starY.png"></image>
- <image class="s_star" for="{{ conHealthStarN }}" src="/common/starN.png"></image>
- </div>
- </div>
- <div class="item_line_bottom" >
- <div class="s_score">
- <text class="label">
- 幸运数字
- </text>
- <text class="score_text">
- {{ conNumber }}
- </text>
- </div>
- <div class="s_score" style="margin-left: 78px;" >
- <text class="label">
- 幸运颜色
- </text>
- <text class="score_text">
- {{ conColor }}
- </text>
- </div>
- </div>
- <div class="list_item_more" >
- <div class="list_item_box1">
- <text class="item_title_text">
- 综合概述
- </text>
- </div>
- <div class="list_item_box2">
- <text class="item_sub_text">
- {{ conSummary }}
- </text>
- </div>
- </div>
- </div>
- </div>
开发过程的问题
- hml文本文字必须写在text标签中
- <text>文字</text>
- hml 动态数据不可以一下 写两个
- <text >// 只会渲染第一个
- {{ conName }} {{ conData }}
- </text>
- <text > // 不支持,直接当文本处理
- {{ conName + conDate }}
- </text>
- hml文件中的{{}},动态数据不支持表达式,比如 5 - 1;
- <div if="{{ flag - 1}}">
- <div if="{{ !flag}}">
- list list-item 标签中无法触发点击事件
- <list @click="handle"> // 无法触发
- <list-item>
- </list>
css部分
代码部分
- .normal_container {
- width: 100%;
- height: 100%;
- display: flex;
- flex-wrap: wrap;
- background: linear-gradient(to right, #6a11cb 0%, #667eea 100%);
- }
- .container_top{
- display: flex;
- }
- /*2X2 布局样式 */
- .top_left{
- width: 165px;
- height: 150px;
- display-index: 3;
- display: flex;
- flex-direction: column;
- padding: 12px 10px;
- }
- .left_item{
- margin-top: 8px;
- }
- .left_item_text{
- font-size: 16px;
- color: #ffffff;
- }
- .conDate{
- font-size: 14px;
- }
- .conPari_text{
- font-size: 18px;
- margin-left: 6px;
- }
- .s_star_2X2{
- width: 16px;
- height: 16px;
- margin-left: 2px;
- }
- .label_2X2{
- margin-right: 8px;
- font-size: 16px;
- color: #fff;
- }
- /*2X4 布局样式右 */
- .top_right{
- width: 150px;
- height: 150px;
- display-index: 2;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .con_image{
- width: 120px;
- height: 120px;
- border-radius: 70px;
- border: 4px solid #fdfbfb;
- }
- /*4X4 布局样式下 */
- .container_bottom{
- width: 100%;
- display-index: 1;
- padding: 0px 10px;
- display: flex;
- flex-direction: column;
- }
- .list_item{
- margin-bottom: 6px;
- }
- .list_item_more{
- display: flex;
- flex-direction: column;
- }
- .list_item_box2{
- width: 100%;
- height: 80px;
- margin-top: -5px;
- }
- .item_title_text{
- font-size: 18px;
- color: #fff;
- padding: 8px 0 0px;
- }
- .item_sub_text{
- font-size: 14px;
- color: #fff;
- /*配套使用*/
- text-overflow: ellipsis;
- max-lines: 3;
- /*配套使用*/
- line-height: 22px;
- }
- .item_line_bottom{
- border-bottom: 1px solid #eee;
- padding-bottom: 8px;
- }
- .item_line_top{
- border-top: 1px solid #eee;
- padding-top: 8px;
- }
- /* 通用样式 */
- /* 评分星 */
- .s_score{
- display: flex;
- align-items: center;
- }
- .s_star{
- width: 14px;
- height: 14px;
- margin-left: 2px;
- }
- .label{
- margin-right: 8px;
- font-size: 14px;
- color: #fff;
- }
- .score_text{
- font-size: 14px;
- color: #fff;
- margin-left: 6px;
- }
遇到的问题
- css boder-raduis 不支持百分比
- css 不支持 嵌套
- .divClass .text{} // 不支持
- css font-size color 不支持继承,font-size color等文本必须写在text标签内,写在父级就不会相应
- css 不支持大部分伪类选择器(:nth-child(n)、:before、:after)
json部分
- {
- "data": {
- "imgSrc": "/common/constellation/天蝎.jpg",
- "conName": "天蝎座",
- "conDate": "2021年07月27日",
- "conPari": "金牛座",
- "conNumber": 1,
- "conColor": "古铜色",
- "conSummary": "有些思考的小漩涡,可能让你忽然的放空,生活中许多的细节让你感触良多,五味杂陈,常常有时候就慢动作定格,想法在某处冻结停留,陷入一阵自我对话的沉思之中,这个时候你不喜欢被打扰或询问,也不想让某些想法曝光,个性变得有些隐晦。",
- "conAllStarY": [],
- "conAllStarN": [],
- "conLoveStarY": [],
- "conLoveStarN": [],
- "conMoneyStarY": [],
- "conMoneyStarN": [],
- "conWorkStarY": [],
- "conWorkStarN": [],
- "conHealthStarY": [],
- "conHealthStarN": []
- },
- "actions": {
- "clickToApp": {// 跳转事件,跳转到相应内容页
- "action": "router",
- "abilityName": "com.example.fortune.MainAbility",
- "params": {
- "message": ""// 点击传递数据
- }
- }
- }
- }
遇到的问题
- json中定义数组,如 “conAllStarY”: [“Y”, “Y”], 在java中动态修改它为[“Y”, “Y”, “Y”, “Y”],则实际渲染出来有问题。打印出来的数组正确,for循环出来的数组只有两位。最后将初始数组改为空数组才解决了这个bug。
- <text>{{ conWorkStarY }}</text>
- <text for="{{conWorkStarY}}" >a</text>
结果:
[“Y”, “Y”, “Y”, “Y”]aa
java代码逻辑
公共类
- public class UpdataJSData {
- private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, UpdataJSData.class.getName());
- // 数据请求
- public static void getJsonData(String constellation, ZZRCallBack.CallBackString callBackName ) {
- String url = "http://web.juhe.cn:8080/constellation/getAll";
- Map<String,String> map = new HashMap<>();
- map.put("key", "9d075905a3e1c33224ac5bb42f4de458");
- map.put("consName", constellation);
- map.put("type", "today");
- ZZRHttp.get(url, map, callBackName);
- }
- // 将请求到的数据更新到fortuneData中,并返回回去
- public static ZSONObject setJson(String response) {
- ZSONObject fortuneData = ZSONObject.stringToZSON(response);
- int error_code = fortuneData.getIntValue("error_code");
- if (error_code == 0) {
- HiLog.info(TAG,"返回的数据正确,设置js中的json数据");
- String[][] all = formatConversion(fortuneData.getString("all"));
- String[][] love = formatConversion(fortuneData.getString("love"));
- String[][] money = formatConversion(fortuneData.getString("money"));
- String[][] work = formatConversion(fortuneData.getString("work"));
- String[][] health = formatConversion(fortuneData.getString("health"));
- HiLog.info(TAG,health.toString());
- fortuneData.put("imgSrc", "/common/constellation/" + fortuneData.getString("name").toString().substring(0, 2) + ".jpg");
- fortuneData.put("conName", fortuneData.getString("name"));// 星座名称
- fortuneData.put("imgSrc", "/common/constellation/" + fortuneData.getString("name").toString().substring(0, 2) + ".jpg");// 星座图片路径拼接
- fortuneData.put("conName", fortuneData.getString("name"));// 星座名称
- fortuneData.put("conDate", fortuneData.getString("datetime"));// 当前事件
- fortuneData.put("conPari", fortuneData.getString("QFriend"));// 速配星座
- fortuneData.put("conNumber", fortuneData.getString("number"));// 幸运数组
- fortuneData.put("conColor", fortuneData.getString("color"));// 幸运颜色
- fortuneData.put("conSummary", fortuneData.getString("summary"));// 描述信息
- fortuneData.put("conAllStarY", all[0]);// 综合评分 标记星
- fortuneData.put("conAllStarN", all[1]);// 综合评分 未标记星
- fortuneData.put("conLoveStarY", love[0]);// 爱情评分 标记星
- fortuneData.put("conLoveStarN", love[1]);// 爱情评分 未标记星
- fortuneData.put("conMoneyStarY", money[0]);// 理财评分 标记星
- fortuneData.put("conMoneyStarN", money[1]);// 理财评分 未标记星
- fortuneData.put("conWorkStarY", work[0]);// 工作评分 标记星
- fortuneData.put("conWorkStarN", work[1]);// 工作评分 未标记星
- fortuneData.put("conHealthStarY", health[0]);// 健康评分 标记星
- fortuneData.put("conHealthStarN", health[1]);// 健康评分 未标记星
- } else {
- HiLog.info(TAG,"请求出错,code=" + error_code);
- }
- return fortuneData;
- }
- // 对分数数据进行封装,转换为二维数组
- private static String[][] formatConversion(String star) {
- int myData = Integer.parseInt(star);
- int myDataY = myData / 20;
- int myDataN = 5 - myDataY;
- HiLog.info(TAG,"star=" + star + "myDataY=" + myDataY + " myDataN=" + myDataN);
- String myListY[] = new String[myDataY];
- String myListN[] = new String[myDataN];
- for(int i = 0; i < myDataY; i++) {
- myListY[i] = "Y";
- }
- for(int i = 0; i < myDataN; i++) {
- myListN[i] = "N";
- }
- String[][] myList = {myListY, myListN};
- return myList;
- }
- }
数据初始化
- 创建卡片时,调用MainAbility中onCreateForm方法
- 实现代码
- protected ProviderFormInfo onCreateForm(Intent intent) {
- HiLog.info(TAG, "更新数据:onCreateForm");
- ProviderFormInfo providerFormInfo = new ProviderFormInfo();
- 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_2X2);
- HiLog.info(TAG, "onCreateForm: formId=" + formId + ",formName=" + formName);
- FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
- FormController formController = formControllerManager.getController(formId);
- formController = (formController == null) ? formControllerManager.createFormController(formId,
- formName, dimension) : formController;
- if (formController == null) {
- HiLog.error(TAG, "Get null controller. formId: " + formId + ", formName: " + formName);
- return null;
- }
- // 调用公共类中获取数据,传入一个callback方法
- UpdataJSData.getJsonData("处女座", new ZZRCallBack.CallBackString() {
- public void onFailure(int code, String errorMessage) {
- //http访问出错了,此部分内容在主线程中工作;
- //可以更新UI等操作,请不要执行阻塞操作。
- HiLog.info(TAG,"更新数据:数据请求失败"+code+","+errorMessage);
- }
- public void onResponse(String response) {
- //http访问成功,此部分内容在主线程中工作;
- //可以更新UI等操作,但请不要执行阻塞操作。
- HiLog.info(TAG,"更新数据:数据请求成功");
- ZSONObject fortuneData = setJson(response);
- try {
- HiLog.info(TAG,"更新数据");
- updateForm(formId, new FormBindingData(fortuneData));
- } catch (FormException e) {
- HiLog.info(TAG,"更新数据失败");
- }
- }
- });
- return providerFormInfo;
- }
采用定时刷新,星座运势不需要周期性刷新
- config.js 中设置定时刷新的时间
- "forms": [
- {
- "scheduledUpdateTime": "10:12", // 定时刷新时间点
- "updateEnabled": true, // 是否周期性刷新
- "updateDuration": 0,
- ...
- }
- ]
- 定时刷新会触发widget->WidgetImpl文件的updateFormData方法
- public void updateFormData(long formId, Object... vars) {
- HiLog.info(TAG, "更新数据");
- UpdataJSData.getJsonData("双鱼座", new ZZRCallBack.CallBackString() {
- public void onFailure(int code, String errorMessage) {
- //http访问出错了,此部分内容在主线程中工作;
- //可以更新UI等操作,请不要执行阻塞操作。
- HiLog.info(TAG,"数据请求失败"+code+","+errorMessage);
- }
- public void onResponse(String response) {
- //http访问成功,此部分内容在主线程中工作;
- //可以更新UI等操作,但请不要执行阻塞操作。
- HiLog.info(TAG,"数据请求成功");
- ZSONObject fortuneData = setJson(response);
- try {
- HiLog.info(TAG,"更新数据");
- ((MainAbility)context).updateForm(formId, new FormBindingData(fortuneData)); //调用MainAbility的方法updateForm(),并将formBindingData作为第二个实参
- } catch (FormException e) {
- HiLog.info(TAG,"更新数据失败");
- }
- }
- });
- }
内容页部分
- 内容页布局比4X4的卡片多了个输入框,可进行星座搜索
- 内容页布局和css同卡片比较相似,这边就贴一下js代码
- import http from '@ohos.net.http';// 导入http
- export default {
- data: {
- conName: "天蝎座",
- options: {
- key: "9d075905a3e1c33224ac5bb42f4de458",
- type: "today"
- },
- url: "",
- conData: {
- name: "天蝎座",
- datetime: "2021年7月28号",
- img: "/common/images/constellation/天蝎.jpg",
- QFriend: "金牛",
- number: "4",
- color: "粉色",
- summary: "有些思考的小漩涡,可能让你忽然的放空,生活中许多的细节让你感触良多,五味杂陈,常常有时候就慢动作定格,想法在某处冻结停留,陷入一阵自我对话的沉思之中,这个时候你不喜欢被打扰或询问,也不想让某些想法曝光,个性变得有些隐晦。"
- },
- responseData: {}
- },
- onInit() {
- this.getHttp();// 发送请求
- },
- // 输入框改变触发
- changeCon(v) {
- this.conName = v.value;
- console.log(v.value+ 'change conName');
- },
- getHttp() {
- this.url = `http://web.juhe.cn:8080/constellation/getAll?key=${this.options.key}&consName=${this.conName}&type=${this.options.type}`
- let httpRequest = http.createHttp();
- httpRequest.request(this.url, {
- method: 'GET'
- },(err, data) => {
- if (err == null) {
- let conData = JSON.parse(data.result);
- console.log('Result:' + data.result);
- if (conData.error_code == 0) {
- conData.img = `/common/images/constellation/${(conData.name).slice(0, 2)}.jpg`
- conData.allY = this.getNewArr(conData.all, "Y");
- conData.allN = this.getNewArr(conData.all, "N");
- conData.healthY = this.getNewArr(conData.health, "Y");
- conData.healthN = this.getNewArr(conData.health, "N");
- conData.loveY = this.getNewArr(conData.love, "Y");
- conData.loveN = this.getNewArr(conData.love, "N");
- conData.moneyY = this.getNewArr(conData.money, "Y");
- conData.moneyN = this.getNewArr(conData.money, "N");
- conData.workY = this.getNewArr(conData.work, "Y");
- conData.workN = this.getNewArr(conData.work, "N");
- this.conData = conData;
- } else {
- console.error("请求失败");
- }
- } else {
- console.log('error:' + JSON.stringify(err));
- }
- });
- },
- // 数字转化为数组不然for循环不出来
- getNewArr(score, type){
- score = parseInt(parseInt(score) / 20);
- let arr = [];
- if (type === "N") {
- console.log('type' + type)
- score = 5 - score;
- for (var i = 0; i < score; i++) {
- arr.push(type);
- }
- } else {
- for (var i = 0; i < score; i++) {
- arr.push(type);
- }
- }
- return arr;
- }
- }
- 需要注意hml中for循环时,最好绑定tid,不然会复用。
- 搜索框的value不是双向绑定的,这边需要监听个change事件,来改变value。
- js部分数据请求时,采用api6,需要自己更改配置设置为兼容api6 官方文档
总结
- JSUI部分,主要是一些布局和样式类,是无法进行数据处理的。做一些弱交互的卡片是非常棒的。
- Java部分主要也就是用到了三个函数。onStart:主要做一些js方事件处理。onCreateForm:创建卡片的时候需要。updateFormData:定时刷新和周期性刷新会走到这里
- 卡片开发目前来说bug还是挺多,限制也挺多的,期待做的越来越好。