手把手带你完成OpenHarmony藏头诗App

1,266 阅读6分钟

1. 介绍

天行数据发现一个有趣的Api接口-藏头诗生成,只要输入特定的内容就能生成藏头诗句,借着我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿,使用DevEco Studio 3.0 Beta4 实现一款运行在OpenHarmony操作系统上的应用程序。

2. 开发环境

  1. 开发工具:DevEco Studio 3.0 Beta4
  2. 申请藏头诗生成API接口 - 天行数据TianAPI,在个人中心 ---> 数据管理 ---> 我的密钥KEY中获取分配的APIKEY值,后续请求数据时需要。
  3. 运行设备:DAYU200开发板

3. 知识要点

序号 要点 描述 涉及知识点
1 创建项目 使用DevEco Studio创建项目 DevEco Studio 工具使用
2 路由 页面跳转 @ohos.router
32 定时器 启动页三秒后跳转到首页 setTimeout()
4 弹窗 消息提示 @ohos.prompt
5 工具集 日志工具(LogUtil) @ohos.hilog
字符工具(StrUtil)
Restful Api 返回结果(RestApiUtil)
请求数据(RequestUtil)@ohos.net.http

4. 实现

1) 创建项目tetcl_poetry

a. 打开DevEco Studio开发者工具(如果没有则2中开发工具链接下载安装即可),点击欢迎页Create Project(创建项目),打开创建项目向导,如下图所示:

image.png

b. 选择OpenHarmony页签中的Empty Ability空模板,点击Next按钮进入Configure Your Project(项目配置)页面,如下图所示:

image.png

  • Project name: 项目名称。
  • Project type: 项目类型。当前提供Application(应用程序)和Atomic service(服务程序),两者区别在于前者在主屏幕上存在应用程序图标,后者则无,后者需要通过服务小部件或二维码等方式访问。
  • Bundle name:包名。建议以域名反写的方式+项目名称。
  • Save location: 项目存储路径。
  • Compile SDK: 编译版本。当前有8和9,9当前不稳定,不建议选择。
  • Model: 模型。如果编译版本选择9时,会出现Stage模型和FA模型。
  • Enable Super Visual: 开启低代码开发。
  • Language: 代码编写语言。当前提供eTS和JS,eTS写法类似与Flutter,JS写法类似于小程序。
  • Compatible SDK:兼容SDK版本
  • Device type: 设备类型
  • Show in service center: 是否展示在服务中心

c. 点击Finish按钮完成项目创建,同时会使用npm加载一些必须的包。加载完成后,打开entry/src/main/ets/MainAbility/pages/index.ets,然后点击右侧Previewer预览器查看初始项目效果,如下图所示:

image.png

至此,完成了项目创建。

2) 路由跳转介绍@ohos.router

一个简单的单页面App就不需要跳转,而复杂的App就会涉及到页面间跳转,OpenHarmony提供了两种方式,一种是容器组件Navigator,一种是@ohos.router模块,本示例是以@ohos.router作为页面跳转。

@ohos.router有多种跳转方式:

方法描述
push跳转到应用内的指定页面
replace用应用内的某个页面替换当前页面,并销毁被替换的页面
back返回上一页面或者指定的页面

在本示例中,启动页跳转到首页,因此选择replace跳转后销毁启动页,防止返回物理键点击后返回到启动页。

replace需要传入RouterOptions参数,其中url为跳转页面(此页面必须在config.json配置文件的js/pages标签中存在)

// config.json
{
    ...
    "module": {
        ...
        "js": [
            ...
            "pages": [
                "pages/splash",
                "pages/index"
            ]
            ...
        ]
        ...
    }
    ...
}

// splash页面跳转到index页面,并销毁splash页面
import router from '@ohos.router'

router.replace({
    url: "pages/index"
})

3) 定时器介绍setTimeout

设置一个定时器,在定时器到期后执行一个函数,如实现启动页等待3秒后跳转页面。

setTimeout(() => {
    // 需要执行的业务逻辑代码
}, 延迟毫秒数)

// splash.ets
// aboutToAppear()方法是页面
aboutToAppear() {
  setTimeout(() => {
    router.replace({
      url: 'pages/index'
    })
  }, 3000)
}

4) 弹窗介绍@ohos.prompt

弹窗消息提示,作为一种为用户提供反馈的能力,简短且明显,并不会给用户造成过多困扰。@ohos.prompt模块中showToast文本提示框能够有效的给用户操作一些反馈,如请求数据后发生的错误,点击提交后反馈是否成功等。本实例用于处理数据请求后反馈异常结果。showToast()方法需要传入ShowToastOptions参数。

prompt.showToast({
    message: '', //显示的文本信息
    duration: 1500, //显示的时间毫秒数,默认1500,取值1500-10000,若小于则显示默认
    bottom: 10, //设置弹窗边框距离屏幕底部的位置
})

// RequestUtils.ets
let request = http.createHttp();
request.request(url, {
    method,
    header: {
        "Content-Type": method === http.RequestMethod.GET ? "application/json" : "application/x-www-form-urlencoded;charset=UTF-8"
    },
    extraData: data
}).then((res) => {
    ...
}).catch((err) => {
    prompt.showToast({
        message: "系统级异常:" + JSON.stringify(err),
        duration: 2000
    })
})

5) 日志工具@ohos.hilog

日志调试方法是除断点调试的另一种方法,能够给开发者调试应用程序带来便利。OpenHarmony提供了两种日志打印的方式,一种是console但已经停止维护了,另一种是@ohos.hilog日志打印,官方也建议使用这种方式。可以按照指定级别、标识和格式字符串输出日志内容。在打印日志前都需要使用isLoggable()方法判断指定领域标识、日志标识和级别是否可以打印,如果写在具体的业务中,代码冗余且不美观,因此做二次封装。

import hilog from '@ohos.hilog';
const TAG = '[Tetcl Poetry]';
const DOMAIN = 0x6699;

export default class LogUtil {

    /**
     * 打印信息
     * @param tag 页签标记
     * @param log 提示信息
     */
    static printInfo(tag: string, log: string) {
        if (hilog.isLoggable(DOMAIN, TAG, hilog.LogLevel.INFO)) {
            hilog.info(DOMAIN, tag, `----- ${TAG} ---> tag: ** ${tag} ** ---> info: %{public}s -----`, log);
        } else {
            this.printWarning(tag, "领域标识或日志标识暂不支持打印");
        }
    }
    
    ... // 其他参见具体代码
}

6) 字符工具

对于字符串处理,很多时候都需要判空、是否为数字、是否数字字母组合、是否手机号等,因此抽离成公用方法。

/**
 * @Description 字符串处理工具
 * @date 2022/7/18 14:20
 * @Author Bai XiaoMing
 * @Version 1.0
 */

export default class StrUtil {
    /**
     * 是否为空
     * @param str
     */
    static isEmpty(str: string) {
        return str === null || str === "";
    }

    /**
     * 不为空
     * @param str
     */
    static isNotEmpty(str: string) {
        return !this.isEmpty(str);
    }

    /**
     * 是否为真空
     * @param str
     */
    static isBlank(str: string) {
        return str === null || /^\s*$/.test(str);
    }

    /**
     * 不为真空
     * @param str
     */
    static isNotBlank(str: string) {
        return !this.isBlank(str);
    }
    
    ... // 其他参见具体代码
}

7) Restful Api返回结果

@ohos.net.http请求数据返回结果后,需要处理成自己的Api返回结果,因此需要一个统一的返回数据类来做解析。 本实例是以藏头诗生成API接口 - 天行数据TianAPI返回数据为例,读者也可以更具自己的Api返回结果进行封装。

/**
 * @Description 返回数据工具
 * @date 2022/7/18 9:41
 * @Author Bai XiaoMing
 * @Version 1.0
 */
export default class RestApiUtil {
    code: number;
    msg: string;
    newslist: Array<any>;

    constructor(code: number, msg: string, newslist: Array<any>) {
        this.code = code;
        this.msg = msg;
        this.newslist = newslist;
    }
}

8) 请求数据介绍@ohos.net.http

@ohos.net.http提供的数据请求,每一个httpRequest对应一个http请求任务,是不可复用的,所以在获取业务数据时,都需要创建一个请求任务,这种方式不便于后期维护,因此做二次封装,并提供getpost 方法。

/**
 * @Description 请求数据工具
 * @date 2022/7/18 9:36
 * @Author Bai XiaoMing
 * @Version 1.0
 */

import prompt from '@ohos.prompt';
import http from '@ohos.net.http';

import RestApi from './RestApiUtil';
import log from './LogUtil';

const TAG = "RequestUtil";

class RequestUtil {

    get(url: string, data?: any): Promise<any> {
        return new Promise((resolve) => {
            this.request(url, http.RequestMethod.GET, data).then((res) => {
                resolve(res);
            })
        })
    }

    post(url: string, data?: any): Promise<any> {
        return new Promise((resolve) => {
            this.request(url, http.RequestMethod.POST, data).then((res) => {
                resolve(res);
            })
        })
    }

    private request(url: string, method: http.RequestMethod.GET | http.RequestMethod.POST, data?: any): Promise<any> {
        return new Promise((resolve) => {
            let request = http.createHttp();
            request.request(url, {
                method,
                header: {
                    "Content-Type": method === http.RequestMethod.GET ? "application/json" : "application/x-www-form-urlencoded;charset=UTF-8"
                },
                extraData: data
            }).then((res) => {
                log.printInfo(TAG, JSON.stringify(res));
                let { responseCode, result } = res;
                if (responseCode === 200) {
                    log.printInfo(TAG, result.toString());
                    let data = result.toString();
                    let resultVal: RestApi = JSON.parse(data);
                    resolve(resultVal);
                } else {
                    prompt.showToast({
                        message: "HTTP级异常:" + res.responseCode,
                        duration: 2000
                    })
                }
            }).catch((err) => {
                prompt.showToast({
                    message: "系统级异常:" + JSON.stringify(err),
                    duration: 2000
                })
            })
        })
    }
}

let request = new RequestUtil();
export default request;

9) 启动页

封装完成后,接下来进行藏头诗App的具体代码编写,首先实现启动页,启动页作为首次打开应用程序显示的页面,展示应用程序Logo及一些说明文字,如下图所示:

image.png

import router from '@ohos.router';
@Entry
@Component
struct Splash {

  build() {
    Flex({direction: FlexDirection.Column}) {
      Flex({justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
        Image($r('app.media.icon'))
          .width(120).height(120)
      }
      .layoutWeight(2)
      Column({space: 10}) {
        Text('字中含意,意中有情')
          .fontSize(24).fontWeight(FontWeight.Bold).fontColor($r('app.color.main_font_color'))
        Text('The meaning of the word is sentimental')
          .fontSize(18).fontWeight(FontWeight.Bold).fontColor($r('app.color._font_color'))
      }
      .layoutWeight(1)
    }
    .width('100%').height('100%')
  }

  aboutToAppear() {
    setTimeout(() => {
      router.replace({
        url: 'pages/index'
      })
    }, 3000)
  }
}

10) 首页

首页即藏头诗App具体的业务实现,根据藏头诗生成API接口 - 天行数据TianAPI可以看到需要必填参数藏字内容和一些选填参数的生成藏头诗参数区,以及结果展示区域,如下图所示:

04e3ae20b248a751e431de0a15e9d3c.png

// 配置参数区
Column() {
  Column({space: 20}) {
    Flex() {
      TextInput({placeholder: "请输入藏字内容...", controller: this.textInputController})
        .height(64)
        .placeholderColor($r('app.color.tips_font_color'))
        .placeholderFont({size: 18, weight: 400})
        .backgroundColor(0xffffff)
        .onChange((value) => {
          this.word = value;
        })
    }
    Column({space: 4}) {
      Text('点击按钮配置诗句指标:')
        .fontSize(14).fontColor($r('app.color._font_color'))
      Divider().color($r('app.color.white')).strokeWidth(1);
    }
    .alignItems(HorizontalAlign.Start)
    .width('100%')
    Row({space: 6}) {
      Button("诗句格式")
        .fontSize(14)
        .backgroundColor(0xfeb92e)
        .onClick(() => {
          TextPickerDialog.show({
            range: Constant.PoetryFormat,
            selected: this.lenSelect,
            onAccept: (value: TextPickerResult) => {
              this.lenSelect = value.index;
              if (value.index === 0) this.len = 0;
              else this.len = value.index - 1;
            }
          })
        })
      Button("藏头位置")
        .fontSize(14)
        .backgroundColor(0xfc6e00)
        .onClick(() => {
          TextPickerDialog.show({
            range: Constant.PoetryType,
            selected: this.typeSelect,
            onAccept: (value: TextPickerResult) => {
              this.typeSelect = value.index;
              if (value.index === 0) this.type = 1;
              else this.type = value.index;
            }
          })
        })
      Button("押韵类型")
        .fontSize(14)
        .backgroundColor(0x0ed8b0)
        .onClick(() => {
          TextPickerDialog.show({
            range: Constant.PoetryPat,
            selected: this.patSelect,
            onAccept: (value: TextPickerResult) => {
              this.patSelect = value.index;
              if (value.index === 0) this.pat = 0;
              else this.pat = value.index;
            }
          })
        })
    }
    .width('100%')
    Flex({justifyContent: FlexAlign.End}) {
      Button('生成')
        .width(200).height(60)
        .fontSize(16).fontWeight(FontWeight.Bold)
        .backgroundColor(0x007aff)
        .onClick(() => {
          if (str.isEmpty(this.word)) {
            prompt.showToast({
              message: "生成藏头诗内容不能为空!",
              duration: 2000
            })
          } else if (this.word.length < 2 || this.word.length > 8) {
            prompt.showToast({
              message: "生成藏头诗内容长度为2-8字符!",
              duration: 2000
            })
          } else {
            this.getPoetry();
          }
        })
    }
    .width('100%')
  }
  .width('90%')
  .padding(16)
  .borderRadius(16)
  .backgroundColor(0xf2f2f2)
}
.width('100%')


// 显示结果区
Swiper(this.swiperController) {
  ForEach(this.poetryArray, (item: any, index: number) => {
    Text(item.list)
      .width('90%').height(160)
      .linearGradient({
        angle: 135,
        direction: GradientDirection.LeftBottom,
        colors: Constant.BgColors[index]
      })
      .margin(10)
  }, (item: any) => item.list)
}
.autoPlay(true)
.loop(true)

说明

有兴趣的读者可以到OpenHarmony App Demo仓中获取项目tetcl_poetry

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿