ArkUI 开发实例:快五一了,做一个回家的订票动画界面(中)

865 阅读10分钟

背景

承接上篇,我们已经实现了整个页面的布局,首先我们再次看一下最终效果吧。

订票动画.gif

继续实例

日期选择器 (DatePicker)

image.png

上篇中,我们完成了布局结构,其中中间是一个日期选择器,我们需要实现用户选择日期后,查询其所选日期的功能,这时候就需要获取用户选择的日期了,这对于ArkUI提供的DatePicker组件还是很方便实现的。

那么我们了解下本篇第一个知识点DatePicker

日期选择器 (DatePicker)通过滑动选择器,实现用户交互式选择日期功能。

根据指定日期范围创建日期滑动选择器。

DatePicker(options?: DatePickerOptions)

ArkUI提供了DatePickerOptions配置日期选择器组件的参数。

其包含以下三个参数:

名称类型必填说明
startDate指定选择器的起始日期。 默认值:Date(‘1970-1-1’)
endDate指定选择器的结束日期。 默认值:Date(‘2100-12-31’)
selectedDate设置选中项的日期。 默认值:当前系统日期

那么如果我们需要设置2024年1月1日至2025年1月1日的日期范围,只需要设置参数的start和end值即可。此时代码如下:

DatePicker({
  start: new Date('2024-1-1'),
  end: new Date('2025-1-1'),
})

好了范围设定好了,我们需要将用户选择的日期,保存下来,此时我们就需要用到selected参数了,在当前视图build() 前添加这段代码, 保存用户选择的日期值,其默认值是新的当前日期对象

private selectedDate: Date = new Date()

此时DatePicker内增加selected参数

DatePicker({
  start: new Date('2024-1-1'),
  end: new Date('2025-1-1'),
  selected: this.selectedDate
})

时间选择器还有些颜色值和效果和我们默认有些差别,我们需要对其部分属性进行修改,让我们了解下DatePicker的一些特有属性。

名称参数类型描述
lunarboolean日期是否显示农历。 - true:展示农历。 - false:不展示农历。 默认值:false
disappearTextStylePickerTextStyle设置所有选项中最上和最下两个选项的文本颜色、字号、字体粗细。 默认值: { color: ‘#ff182431’, font: { size: ‘14fp’, weight: FontWeight.Regular } }
textStylePickerTextStyle设置所有选项中除了最上、最下及选中项以外的文本颜色、字号、字体粗细。 默认值: { color: ‘#ff182431’, font: { size: ‘16fp’, weight: FontWeight.Regular } }
selectedTextStylePickerTextStyle设置选中项的文本颜色、字号、字体粗细。 默认值: { color: ‘#ff007dff’, font: { size: ‘20vp’, weight: FontWeight.Medium } }

我们可以看到其默认是不显示农历的,故lunar属性可以不写,我们主要是修改下选择部分文字颜色,需要用到selectedTextStyle属性,此时DatePicker增加该属性,代码如下:

DatePicker({
  start: new Date('2024-1-1'),
  end: new Date('2025-1-1'),
  selected: this.selectedDate
})
  .selectedTextStyle({ color: '#132968', font: { size: '18', weight: FontWeight.Regular } })

最后,我们通过选择日期时触发的事件保存用户选择的日期到selectedDate中,看下DatePicker支持的变量:

名称功能描述
onDateChange(callback: (value: Date) => void)选择日期时触发该事件。 Date:返回选中的时间。

最后,我们增加当前onDateChange事件,代码如下:

atePicker({
    start: new Date('2024-1-1'),
    end: new Date('2025-1-1'),
    selected: this.selectedDate
  })
    .selectedTextStyle({ color: '#132968', font: { size: '18', weight: FontWeight.Regular } })
    .onDateChange((value: Date) => {
      this.selectedDate = value
    })
.padding(3)
.height(165)
.width(230)

日期选择器完成,那么接下来实现,查询用户选择的日期当天的火车票。也就是下一个组件Button按钮实现了。

按钮 (Button)

Button通常用于响应用户的点击操作并执行相应的功能事件。

image.png

其包含以上几种普通样式。

image.png

也可以通过包裹子元素实现图文混合的按钮。

image.png

基于我们的UI设计稿,我们需要的是带红色的纯文字按钮。

只需要在Button内部设置Text组件即可实现,颜色值则通过其backgroundColor属性实现,其他圆角和位置我们就通过布局完善和UI一样的效果即可。代码如下:

Button({ type: ButtonType.Normal }) {
  Text( "开始查询")
    .fontSize(18)
    .fontColor(Color.White)
    .margin({ left: 80, right: 80, top: 5, bottom: 5 })
}
.backgroundColor("#FC6A68")

image.png

最终我们需要这个“开始查询”按钮点击查询的功能,基于UI稿,我们知道用户点击按钮后就显示当前日期的车票信息,包含“日期”、“时间”、“车次”、“车厢”、“座位号”、“票价”这些,因为本次实例仅做基本演示,故除了“日期”外其他数据均写个文本即可,真实开发这些数据需要通过服务器反馈查询结果。

故,此时点击“开始查询”按钮会发生两件事情,

  1. “开始查询”按钮变成“确认订票”按钮。
  2. 隐藏日期选择器,显示查询的车票信息

我们需要用到@State装饰器判断用户点击“开始查询”按钮前后状态。

那么我们继续了解下一个知识点:@State装饰器

@State装饰器:组件内状态

@State装饰的变量又名状态变量,当我们在变量前加上@State装饰器后,其就和组件绑定起来,实现变量控制组件变化的显示的效果。

那么我们设定下用户是否点击“开始查询”的状态变量为ifStartSearch,其是默认为false的布尔值,我们需要再build() 前设置该变量,代码如下:

@State ifStartSearch: boolean = false;

当用户点击“开始查询”按钮时,当前变量需要改成true,此时我们为Button添加点击事件,此时Button的代码如下

Button({ type: ButtonType.Normal }) {
  Text( "开始查询")
    .fontSize(18)
    .fontColor(Color.White)
    .margin({ left: 80, right: 80, top: 5, bottom: 5 })
}
.margin(20)
.height(35)
.backgroundColor("#FC6A68")
.borderRadius(8)
.onClick(() => {
  this.ifStartSearch = true
})

发现发现发现 基于上述分析,当用户点击“开始查询”按钮时发生的两件事情,我们首先解决第一件事情,即按钮文字的变化。我们可以借助三元运算符判断不同ifStartSearch状态显示的文字内容,此时Button内Text修改如下:

Text(this.ifStartSearch ? "确认订票" : "开始查询")

接下来需要解决第二件事情,即隐藏日期选择器,显示新的查票界面,此时我们需要借助if实现条件判断。

if/else:条件渲染

条件渲染可根据应用的不同状态,使用if、else和else if渲染对应状态下的组件内容。 其具有以下更新机制:

  1. 基于判断条件,若分支无变化,则不需要继续执行。
  2. 若判断条件发生改变,则删除此前构建组件。
  3. 构建符合判断条件的新组件,将新组件添加到if父容器中。
  4. 如果适用的else不存在,则不构建任何内容。

此时我们将之前DatePicker增加条件渲染判断,同时需要完善新的信息显示组件。代码如下:

if (!this.ifStartSearch) {
  DatePicker({
    start: new Date('2024-1-1'),
    end: new Date('2025-1-1'),
    selected: this.selectedDate
  })
    .selectedTextStyle({ color: '#132968', font: { size: '18', weight: FontWeight.Regular } })
    .onDateChange((value: Date) => {
      this.selectedDate = value
    })
    .padding(3)
    .height(165)
    .width(230)
}
else {
  Column() {
  
  }
  .justifyContent(FlexAlign.SpaceAround)
  .height(165)

当ifStartSearch为true时,需要显示新的内容,基于UI稿和前面内容,我们已经可以很简单知道查询内容怎么写了,其包含上下两个Row布局,每个Row又显示对应的两行文字。其中日期文本需要显示刚刚DatePicker获取的日期。代码如下:

Column() {
  Row({ space: 15 }) {
    Column({ space: 10 }) {
      Text("日期")
        .fontSize(15)
        .fontWeight(FontWeight.Normal)
        .fontColor("#A1A9C3")
      Text((this.selectedDate.getMonth() + 1).toString() + "月" + (this.selectedDate.getDate()).toString() + "日")
        .fontSize(17)
        .fontColor("#132968")
    }
    .width(80)

    Column({ space: 10 }) {
      Text("时间")
        .fontSize(15)
        .fontWeight(FontWeight.Normal)
        .fontColor("#A1A9C3")
      Text("10:08")
        .fontSize(17)
        .fontColor("#132968")
    }
    .width(80)

    Column({ space: 10 }) {
      Text("车次")
        .fontSize(15)
        .fontWeight(FontWeight.Normal)
        .fontColor("#A1A9C3")
      Text("Z225")
        .fontSize(17)
        .fontColor("#132968")
    }
    .width(80)
  }

  Row({ space: 15 }) {
    Column({ space: 10 }) {
      Text("车厢")
        .fontSize(15)
        .fontWeight(FontWeight.Normal)
        .fontColor("#A1A9C3")
      Text("07车")
        .fontSize(17)
        .fontColor("#132968")
    }
    .width(80)

    Column({ space: 10 }) {
      Text("座位号")
        .fontSize(15)
        .fontWeight(FontWeight.Normal)
        .fontColor("#A1A9C3")
      Text("061号")
        .fontSize(17)
        .fontColor("#132968")
    }
    .width(80)

    Column({ space: 10 }) {
      Text("票价")
        .fontSize(15)
        .fontWeight(FontWeight.Normal)
        .fontColor("#A1A9C3")
      Text("210.0元")
        .fontSize(17)
        .fontColor("#132968")
    }
    .width(80)
  }
}
.justifyContent(FlexAlign.SpaceAround)
.height(165)

其中我们通过获取的 this.selectedDate日期的getMonth()和getDate()获取最终月和日,注意,最终需要通过toString()将获取的数字转换成字符串。

月:this.selectedDate.getMonth() + 1).toString()

日:this.selectedDate.getDate()).toString()

那么目前还有下方两个功能没有实现:

  1. 点击切换目的地与出发地

  2. 点击“确认订票”实现订票功能吐司弹窗

这两个功能都是按钮相关的,那么我们一个个解决。

image.png

首先是目的地与出发地的切换,点击切换按钮后,实现目的地和出发地对调。

我们可以根据刚刚的@State装饰器和if/else条件渲染的知识点,对当前按钮也设置变量状态绑定。

首先设置变量,我们继续在build() 前设置该新的变量,代码如下:

@State ifSwitch : boolean = false;

然后在出发地和目的地组件位置,通过条件渲染,设置相反的组件,代码如下:

//出发地
if (!this.ifSwitch) {
  Column({ space: 5 }) {
    Text("信阳")
      .fontSize(20)
      .fontWeight(FontWeight.Medium)
      .fontColor("#132968")
    Text("XinYang")
      .fontSize(13)
      .fontColor("#132968")
  }
  .width(70)
}
else {
  Column({ space: 5 }) {
    Text("深圳")
      .fontSize(20)
      .fontWeight(FontWeight.Medium)
      .fontColor("#132968")
    Text("ShenZhen")
      .fontSize(13)
      .fontColor("#132968")
  }
  .width(70)
}
//目的地
if (!this.ifSwitch) {
  Column({ space: 5 }) {
    Text("深圳")
      .fontSize(20)
      .fontWeight(FontWeight.Medium)
      .fontColor("#132968")
    Text("ShenZhen")
      .fontSize(13)
      .fontColor("#132968")
  }
  .width(70)
}
else {
  Column({ space: 5 }) {
    Text("信阳")
      .fontSize(20)
      .fontWeight(FontWeight.Medium)
      .fontColor("#132968")
    Text("XinYang")
      .fontSize(13)
      .fontColor("#132968")
  }
  .width(70)
 }

image.png

然后就是点击切换按钮后,其本身图标也会改变,因为点击后用户需要重新设定订票日期,所以其Button内需要增加一个修改ifStartSearch为false事件,代码如下:

Button() {
  if (!this.ifSwitch) {
    Image("/pages/ComponentClassification/ExampleComponents/OneReserve/buttonArrow.png")
      .width(40)
  }
  else {
    Image("/pages/ComponentClassification/ExampleComponents/OneReserve/buttonArrowReverse.png")
      .width(40)
  }
}
.backgroundColor("#DEEBF9")
.width(40)
.onClick(() => {
  this.ifSwitch = !this.ifSwitch;
  this.ifStartSearch = false
})

最后,我们最后一个功能,即点击“立即订票”,完成订票功能,我们用一个吐司弹窗代表订票成功。

因为只有ifStartSearch变量为true才会有“立即订票”按钮,所以我们需要再该按钮增加一个if/else条件渲染,当ifStartSearch变量为true时,点击弹出吐司弹窗。

ArkUI的吐司弹窗需要导入弹窗模板,我们在当前代码顶部加入代码:

import promptAction from '@ohos.promptAction';

然后通过该模板的ShowToastOptions命令实现吐司弹窗,让我们看下其包含的属性

名称类型必填说明
messagestring或Resource显示的文本信息。
durationnumber默认值1500ms,取值区间:1500ms-10000ms。若小于1500ms则取默认值,若大于10000ms则取上限值10000ms。
bottomstring或 number设置弹窗边框距离屏幕底部的位置。 默认值:80vp
showModeToastShowMode设置弹窗是否显示在应用之上。 默认值:ToastShowMode.DEFAULT,默认显示在应用内。

我们先简单修改内部message就行,修改为“恭喜您!购票成功,请尽快前往取票窗口取票”

当前Button代码最终如下:

Button({ type: ButtonType.Normal }) {
  Text(this.ifStartSearch ? "确认订票" : "开始查询")
    .fontSize(18)
    .fontColor(Color.White)
    .margin({ left: 80, right: 80, top: 5, bottom: 5 })
}
.margin(20)
.height(35)
.backgroundColor("#FC6A68")
.borderRadius(8)
.onClick(() => {
  if (this.ifStartSearch) {
    promptAction.showToast({
      message: "恭喜您!购票成功,请尽快前往取票窗口取票"
    })
  }
  this.ifStartSearch = true
})

订票无动画.gif

至此,我们已经实现当前实例全部功能,效果如上图所示,但是目前还是毫无动画效果的,那么下一章我们开始制作相关动画吧。敬请期待!