暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

鸿蒙“星座运势”卡片,有点牛啊!

鸿蒙技术社区 2021-09-11
458

本人是 JS 攻城狮,Java 属于初学状态,写的不好的地方还请多多指教!


为什么决定开发一个星座运势服务卡片?主要有以下几点:

  • 老年人和女性都比较关心运势,卡片类型刚好能够清晰展示运势,同时也不需要太多操作,与服务卡片理念契合(弱交互、强服务)。

  • 聚合 API 上刚好有相应接口,可以直接调用,比较方便。


实现效果


实现了三个卡片(2X2、2X4、4X4)和一个内容页,基于 api6 开发(API 6 目前只支持运行在 Phone 设备的模拟器上,其他设备暂不支持运行)。
如何使用 API 6?点击查看:
https://developer.harmonyos.com/cn/docs/documentation/doc-guides/create_new_project-0000001053342414


卡片使用原子化布局实现,根据卡片规格自适应:
https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-components-common-atomic-layout-0000001062070665


内容页可查询关注的星座,调用接口来获取运势:

创建项目


搭建环境,下载开发工具并安装。下载和安装官方文档:
https://developer.harmonyos.com/cn/docs/documentation/doc-guides/installation_process-0000001071425528


打开安装好的 DevEco Studio 软件,创建项目。官方文档:
https://developer.harmonyos.com/cn/docs/documentation/doc-guides/hello_world-0000001054516888


填写创建的工程信息:

  • Project Type:工程的类型。

  • Service:原子化服务。原子化服务调试、运行时,在设备桌面上没有应用图标、免安装的。

  • Application:传统方式需要安装的应用。

  • Compatible API Version:兼容的 SDK 最低版本。

  • Show in Service Center:是否在服务中心露出。如果 Project Type 为 Service,则会同步创建一个 2*2 的服务卡片模板。


官方文档:
https://developer.harmonyos.com/cn/docs/documentation/doc-guides/create_new_project-0000001053342414


创建好的模板,我们主要会用到以下目录:

卡片 JS UI 部分


①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 {
    width100%;
    height100%;
    display: flex;
    flex-wrap: wrap;
    backgroundlinear-gradient(to right, #6a11cb 0%, #667eea 100%);
}
.container_top{
    display: flex;
}
/*2X2 布局样式 */
.top_left{
    width165px;
    height150px;
    display-index3;
    display: flex;
    flex-direction: column;
    padding12px 10px;
}
.left_item{
    margin-top8px;
}
.left_item_text{
    font-size16px;
    color#ffffff;
}
.conDate{
    font-size14px;
}
.conPari_text{
    font-size18px;
    margin-left6px;
}
.s_star_2X2{
    width16px;
    height16px;
    margin-left2px;
}
.label_2X2{
    margin-right8px;
    font-size16px;
    color#fff;
}
/*2X4 布局样式右 */
.top_right{
    width150px;
    height150px;
    display-index2;
    display: flex;
    align-items: center;
    justify-content: center;
}
.con_image{
    width120px;
    height120px;
    border-radius70px;
    border4px solid #fdfbfb;
}
/*4X4 布局样式下 */
.container_bottom{
    width100%;
    display-index1;
    padding0px 10px;
    display: flex;
    flex-direction: column;
}
.list_item{
    margin-bottom6px;
}
.list_item_more{
    display: flex;
    flex-direction: column;
}
.list_item_box2{
    width100%;
    height80px;
    margin-top: -5px;
}
.item_title_text{
    font-size18px;
    color#fff;
    padding8px 0 0px;
}
.item_sub_text{
    font-size14px;
    color#fff;
/*配套使用*/
    text-overflow: ellipsis;
    max-lines3;
/*配套使用*/
    line-height22px;
}
.item_line_bottom{
    border-bottom1px solid #eee;
    padding-bottom8px;
}
.item_line_top{
    border-top1px solid #eee;
    padding-top8px;
}
/* 通用样式 */
/* 评分星 */
.s_score{
    display: flex;
    align-items: center;
}
.s_star{
    width14px;
    height14px;
    margin-left2px;
}
.label{
    margin-right8px;
    font-size14px;
    color#fff;
}
.score_text{
    font-size14px;
    color#fff;
    margin-left6px;
}


遇到的问题:


(1)css boder-raduis 不支持百分比


(2)css 不支持嵌套:

.divClass .text{} // 不支持


(3)css font-size color 不支持继承,font-size color 等文本必须写在 text 标签内,写在父级就不会相应。


(4)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(02) + ".jpg");
            fortuneData.put("conName", fortuneData.getString("name"));// 星座名称
            fortuneData.put("imgSrc""/common/constellation/" + fortuneData.getString("name").toString().substring(02) + ".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(02)}.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。


总结


JS UI 部分,主要是一些布局和样式类,是无法进行数据处理的。做一些弱交互的卡片是非常棒的。

Java 部分主要也就是用到了三个函数:
  • onStart:主要做一些 js 方事件处理。

  • onCreateForm:创建卡片的时候需要。

  • updateFormData:定时刷新和周期性刷新会走到这里


卡片开发目前来说 bug 还是挺多,限制也挺多的,期待做的越来越好。


源码地址:

https://gitee.com/chinasoft6_ohos/forturne


作者:石凡

一周年庆 抽奖活动

关注鸿蒙技术社区订阅号,转发此图文至朋友圈,公众号回复转发的截图,抽鸿蒙新款 matepad11价值 399、299 元的鸿蒙盲盒和 2000ml 健康随身杯


回复“周年庆”抽奖

👇


“阅读原文”了解更多

文章转载自鸿蒙技术社区,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论