定时器、递归、Math、Date

定时器、递归、Math、Date

今日核心:

  1. 定时器、递归、内置对象、常用方法

1. 定时器

日常开发中如果需要代码 延迟一会执行,或者每隔一段时间执行一次,就可以使用定时器
定时器有两种:

  1. setTimeout: 延迟执行
  2. setInterval: 间隔执行

1.1. setTimeout

setTimeout可以用来设置一个定时器,当设置的时间到了之后就会去执行指定的函数
执行一次用 setTimeout

setTimeout、clearTimeout是一个全局函数,可以直接使用

@Entry
@Component
struct Page01_timeoutAndInterval {


  build() {
    Column({ space: 20 }) {
      Text('延时器-Timeout')
        .fontSize(30)
        .width('100%')
        .textAlign(TextAlign.Center)
      Row({ space: 20 }) {
        Button('开启延时器')
          .onClick(() => {
            // 开启延时器
           
          })
        Button('关闭延时器')
          .onClick(() => {
           
          })
      }
    }
    .padding(10)
  }
}

试一试:

  1. 延迟执行逻辑
  2. 获取返回的 定时器 id
  3. 通过定时器 id 清除 已开启的定时器
    tips:
  4. 定时器中传入的函数虽然是延迟执行,但是并【不会阻塞】其他的代码
@Entry
@Component
struct TimeoutDemo {
  // 只是用来保存数据,不影响 UI 可以不加状态修饰符
  timeId?: number

  build() {
    Column({ space: 20 }) {
      Text('延时器-Timeout')
        .fontSize(30)
        .width('100%')
        .textAlign(TextAlign.Center)
      Row({ space: 20 }) {
        Button('开启延时器')
          .onClick(() => {
            // 开启延时器
            let timeId = setTimeout(() => {
              // 延迟执行逻辑
              console.log('代码执行啦')
            }, 2000)

            //  保存定时器 id
            this.timeId = timeId
          })
        Button('关闭延时器')
          .onClick(() => {
            // 调用 clearTimeout 清除定时器
            clearTimeout(this.timeId)
          })
      }
    }
    .padding(10)
  }
}

1.2. setInterval

setInterval 也是可以用来设置一个定时器,根据设置的时间间隔来执行指定的函数
执行多次用 setInterval!!
setInterval、clearInterval是一个全局函数,可以直接使用

// 1. 基本使用
setInterval(()=>{
  // 延迟执行的逻辑
},时间)

// 2. 获取返回的定时器 id
const timeId = setInterval(()=>{
  // 延迟执行的逻辑
},时间)

// 3. 根据定时器 id 清除定时器
clearInterval(timeId)

试一试:

  1. 间隔执行逻辑
  2. 获取返回的 定时器 id
  3. 通过定时器 id 清除 已开启的定时器
@Entry
@Component
struct Page02_interval{
  // 只是用来保存数据,不影响 UI 可以不加状态修饰符
  timeId?: number

  build() {
    Column({ space: 20 }) {
      Text('定时器-Interval')
        .fontSize(30)
        .width('100%')
        .textAlign(TextAlign.Center)
      Row({ space: 20 }) {
        Button('开启延时器')
          .onClick(() => {
            this.timeId = setInterval(() => {
              console.log('代码执行啦~')
            }, 2000)
          })
        Button('关闭延时器')
          .onClick(() => {
            clearInterval(this.timeId)
          })
      }
    }
    .padding(10)
  }
}

1.3. 案例-获取验证码

接下来通过刚刚学习的定时器,来完成一个获取验证码的倒计时功能
在这里插入图片描述
在这里插入图片描述
需求:

  1. 获取验证码
    a. 点击按钮之后,延迟 2 秒(setTimeout)
    b. 获取一个 4 位的随机数,通过弹框显示出来(AlertDialog.show({message:}))
    c. Math.floor(1000+Math.random()*9000)
  2. 倒计时效果
    a. 点击开启倒计时
    ⅰ. @State time = 0
    1. 点了之后->60
      ⅱ. setInterval
    2. time–
    3. time==0 关闭定时器
      b. 切换显示效果
      ⅰ. 默认:发送验证码
      ⅱ. 倒计时中:xx 秒后获取
      ⅲ. 根据 time 是否为 0 来决定
    4. 0:发送验证码
    5. 不为 0:xx 秒后获取
      c. 倒计时中无法重复点击
      ⅰ. 时间为 0 时:可以点击
      ⅱ. 时间不为 0 时:不可以点击
      分析:
@Entry
@Component
struct LoginDemo {
  build() {
    Column() {
      this.titleBuilder()
      TextInput({ placeholder: '请输入手机号' })
        .textInputExtend()
      Divider()
      Row() {
        TextInput({ placeholder: '请输入验证码' })
          .textInputExtend()
          .layoutWeight(1)
        Text('发送验证码')
          .fontSize(14)
          .fontColor(Color.Gray)
      }
      .width('100%')

      Divider()

      Button('登录')
        .width('100%')
        .type(ButtonType.Normal)
        .backgroundColor('#ea6051')
        .margin({ top: 50 })

    }
    .padding({ top: 80, left: 40, right: 40 })
    .width('100%')
    .alignItems(HorizontalAlign.Start)
  }

  @Builder
  titleBuilder() {
    Text('短信登录')
      .fontSize(25)
      .fontWeight(600)
      .margin({ bottom: 30 })
  }
}

@Extend(TextInput)
function textInputExtend() {
  .backgroundColor(Color.White)
  .padding({ left: 0, top: 20, bottom: 20 })
  .placeholderColor('#ccc')
}

参考代码

@Entry
@Component
struct LoginDemo {
  @State time: number = 0

  build() {
    Column() {
      this.titleBuilder()
      TextInput({ placeholder: '请输入手机号' })
        .textInputExtend()
      Divider()
      Row() {
        TextInput({ placeholder: '请输入验证码' })
          .textInputExtend()
          .layoutWeight(1)
        Text(this.time == 0 ? '发送验证码' : `${this.time}秒后获取`)
          .fontSize(14)
          .fontColor(Color.Gray)
          .onClick(() => {
            // 避免倒计时 为 0 时,重复点击
            if (this.time != 0) {
              return
            }
            setTimeout(() => {
              AlertDialog.show({
                message: Math.floor(1000 + 9000 * Math.random())
                  .toString()
              })
            }, 2000)
            this.time = 60
            const timeId = setInterval(() => {
              this.time--
              // 倒计时结束,清空定时器
              if (this.time == 0) {
                clearInterval(timeId)
              }
            }, 1000)
          })
      }
      .width('100%')

      Divider()

      Button('登录')
        .width('100%')
        .type(ButtonType.Normal)
        .backgroundColor('#ea6051')
        .margin({ top: 50 })

    }
    .padding({ top: 80, left: 40, right: 40 })
    .width('100%')
    .alignItems(HorizontalAlign.Start)
  }

  @Builder
  titleBuilder() {
    Text('短信登录')
      .fontSize(25)
      .fontWeight(600)
      .margin({ bottom: 30 })
  }
}

@Extend(TextInput)
function textInputExtend() {
  .backgroundColor(Color.White)
  .padding({ left: 0, top: 20, bottom: 20 })
  .placeholderColor('#ccc')
}

2. 递归

调用自身的函数我们称之为递归函数。

2.1. 基本用法

在某种意义上说,递归近似于循环。两者都重复执行相同的代码,并且两者都需要一个终止条件(避免无限循环,或者在这种情况下更确切地说是无限递归)。

// 没有退出条件的 递归函数--无限递归
function func(){
  func()
}

试一试:

  1. 完成函数剩余部分内容
  2. 使用递归完成
let num: number = 1

function printLog(num: number) {
  console.log(`你好,第${num}次打印`)
   // 完成剩余代码
}

// 打印 10 次
printLog(10)

参考答案

let num: number = 1

function printLog(num: number) {
  console.log(`你好,第${num}次打印`)
  // 递减
  num--
  // 退出条件
  if (num > 1) {
    printLog(num)
  }
}

// 打印 10 次
printLog(10)

2.2. 案例-计算总人数

日常开发中,递归主要用来处理数据,咱们来看一个具体的例子。计算总人数
需求:
● 完成 getTotal函数,获取公司所有部门的总人数
核心步骤:

  1. 先求和最外层
  2. 逐层求和,找到规律
  3. 抽取
interface Department {
  name: string;
  employees: number;
  subDepartments?: Department[];
}
// 示例组织结构
const organization: Department = {
  name: "总裁办",
  employees: 20,
  subDepartments: [
    {
      name: "人事部",
      employees: 20,
      subDepartments: [
        {
          name: "招聘组",
          employees: 10
        },
        {
          name: "培训组",
          employees: 8
        }
      ]
    },
    {
      name: "技术部",
      employees: 50,
      subDepartments: [
        {
          name: "前端组",
          employees: 20
        },
        {
          name: "后端组",
          employees: 25
        },
        {
          name: "测试组",
          employees: 5
        }
      ]
    },
    {
      name: "财务部",
      employees: 30
    }
  ]
};

// 计算部门及其子部门的总员工人数
function getTotalEmployees(department: Department): number {
  // 实现 逻辑
  return 0
}



// 计算总员工人数
const totalEmployees = getTotalEmployees(organization);
console.log("总员工人数:", totalEmployees); // 输出: 总员工人数: 188

@Entry
@Component
struct Page04_recursionDemo {
  @State message: string = '递归求和案例';

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
    }
    .height('100%')
  }
}

参考代码

interface Department {
  name: string;
  employees: number;
  subDepartments?: Department[];
}

// 示例组织结构
const organization: Department = {
  name: "总裁办",
  employees: 20,
  subDepartments: [
    {
      name: "人事部",
      employees: 20,
      subDepartments: [
        {
          name: "招聘组",
          employees: 10
        },
        {
          name: "培训组",
          employees: 8
        }
      ]
    },
    {
      name: "技术部",
      employees: 50,
      subDepartments: [
        {
          name: "前端组",
          employees: 20
        },
        {
          name: "后端组",
          employees: 25
        },
        {
          name: "测试组",
          employees: 5
        }
      ]
    },
    {
      name: "财务部",
      employees: 30
    }
  ]
};

// 计算部门及其子部门的总员工人数
function getTotal(department: Department): number {
  // 定义总人数
  let total = 0
  // 保存当前部门的人数
  total += department.employees

  // 求子部门的
  if (department.subDepartments?.length) {
    // 存在 再求和
   for (const item of department.subDepartments) {
      const result = getTotalEmployees(item)
      total += result
    }
  }


  return total
}


// 计算总员工人数
const totalRes = getTotal(organization);
console.log("总员工人数:", totalEmployees); // 输出: 总员工人数: 188

@Entry
@Component
struct Page04_recursionDemo {
  @State message: string = '递归求和案例';

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
    }
    .height('100%')
  }
}

3. 常用内置对象

3.1. Math 对象

是一个内置对象,它拥有一些数学常数属性和数学函数方法。Math 的所有属性与方法都是静态的,使用的时候直接通过Math点出来即可

3.1.1. 常用属性

Math.pi
圆周率,一个圆的周长和直径之比,约等于 3.14159。

console.log(Math.PI.toString())
3.1.2. 常用方法
方法说明
Math.random()返回一个 0 到 1 之间的伪随机数。
Math.ceil(x)返回大于一个数的最小整数,即一个数向上取整后的值。
Math.floor(x)返回小于一个数的最大整数,即一个数向下取整后的值。
Math.round(x)返回四舍五入后的整数。
Math.abs(x)返回一个数的绝对值
Math.max([x[,y[, …]]])返回零到多个数值中最大值。
Math.min([x[,y[, …]]])返回零到多个数值中最小值。

const numA: number = 1.5
console.log(Math.ceil(numA) + '') // 向上取整 2
console.log(Math.floor(numA) + '') // 向下取整 1
console.log(Math.round(numA) + '') // 四舍五入 2

const numB:number = -9
console.log(Math.abs(numB) + '') // 绝对值 9

const numList: number[] = [13, 2, 31, 42, 15, 56, 27, 28]
const max: number = Math.max(...numList)
const min: number = Math.min(...numList)
console.log('max:', max) // 最大值
console.log('min:', min) // 最小值

// 0-1 取得到0,取不到 1
console.log(Math.random() + '')

// 返回 0-n的随机数的函数
function getRandomArbitrary(max: number): number {
  return Math.floor(Math.random() * (max + 1))
}

// 返回 min-max 的随机数的函数
function getRandomIntInclusive(min: number, max: number): number {
  return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
}

3.2. Date 对象

ArkTS 中另外一个常用的内置对象 Date,他可以用来创建、解析、操作日期和时间。
使用 Date 对象首先需要通过 new 操作符进行实例化

// 获取当前日期
const date1: Date = new Date()

// 获取指定日期
// ISO 8601 格式(YYYY-MM-DDTHH:mm:ss.ssZ)中间用 T 分隔
const date2: Date = new Date('1995-01-01T01:11:00')
// Unix时间戳 是指从1970年1月1日(UTC)开始到现在经历的时间(毫秒)
const date3: Date = new Date(1706170405708)
3.2.1. 常用方法
3.2.1.1. 实例方法
方法名作用说明
getFullYear获取年份4 位数年份
getMonth获取月份取值 0-11
getDate获取日期月份中的日期
getHours获取小时
getMinutes获取分钟
getSeconds获取秒
getDay获取星期周日为 0
3.2.1.2. 静态方法
方法名作用说明
now获取当前时间时间戳
// 大伙执行的时候 即可获取到时间戳啦~
console.log(Date.now()+'')

3.3. 案例-时钟

结合刚刚学习的内容,来完成一个时钟案例
在这里插入图片描述
需求:

  1. 开屏显示
    a. 默认显示当前的 年月日
    b. 星期
    c. 时分秒
  2. 倒计时
    a. 时分秒持续改变

分析:
3. 开屏显示
a. 创建一个 Date 实例 new
b. 根据这个实例,获取对应的日期即可
c. 数量使用 api
4. 倒计时
a. setInterval
b. 页面打开时,组件加载完毕 onAppear()
c. setInterval,间隔 1 秒
5. 小于 10,调整为 2 个数

@Entry
@Component
struct Date01 {
  build() {
    Column() {
      Column() {
        Text('2024年 3 月 24 日 周日')
          .fontColor(Color.White)
          .fontSize(20)
        Stack() {
          Row({ space: 10 }) {
            Text('10')
              .textStyle()
            Text('10')
              .textStyle()
            Text('10')
              .textStyle()
          }

          Divider()
            .strokeWidth(2)
            .color(Color.Black)
        }
        .padding(10)

        Text('Stay hungry,Stay foolish')
          .fontColor(Color.White)
          .fontSize(18)
      }

    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Black)
    .justifyContent(FlexAlign.Center)
  }
}


@Extend(Text)
function textStyle() {
  .width(100)
  .height(100)
  .backgroundColor('#191919')
  .borderRadius(10)
  .textAlign(TextAlign.Center)
  .fontColor(Color.White)
  .fontSize(70)
  .fontWeight(900)
}

参考代码

@Entry
@Component
struct Date01 {
  year: string = new Date().getFullYear()
    .toString()
  month: string = (new Date().getMonth() + 1).toString()
  date: string = new Date().getDate()
    .toString()
  week: number = new Date().getDay()
  // 时分秒
  @State hour: number = new Date().getHours()
  @State minute: number = new Date().getMinutes()
  @State second: number = new Date().getSeconds()

  getWeek(weekNum: number): string {
    switch (weekNum) {
      case 0:
        return '周日'
      case 1:
        return '周一'
      case 2:
        return '周二'
      case 3:
        return '周三'
      case 4:
        return '周四'
      case 5:
        return '周五'
      case 6:
        return '周六'
      default:
        return ''
    }
  }

  build() {
    Column() {
      Column() {
        Text(`${this.year}年 ${this.month} 月 ${this.date} 日 ${this.getWeek(this.week)}`)
          .fontColor(Color.White)
          .fontSize(20)
        Stack() {
          Row({ space: 10 }) {
            Text(this.hour.toString())
              .textStyle()
            Text(this.minute.toString())
              .textStyle()
            Text(this.second.toString())
              .textStyle()
          }
          .onAppear(() => {
            setInterval(() => {
              const date = new Date()
              this.hour = date.getHours()
              this.minute = date.getMinutes()
              this.second = date.getSeconds()
            }, 1000)
          })

          Divider()
            .strokeWidth(2)
            .color(Color.Black)
        }
        .padding(10)

        Text('Stay hungry,Stay foolish')
          .fontColor(Color.White)
          .fontSize(18)
      }

    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Black)
    .justifyContent(FlexAlign.Center)
    .onAppear(() => {

    })
  }
}

@Extend(Text)
function textStyle() {
  .width(100)
  .height(100)
  .backgroundColor('#191919')
  .borderRadius(10)
  .textAlign(TextAlign.Center)
  .fontColor(Color.White)
  .fontSize(70)
  .fontWeight(900)
}

4. String

String 提供了不少的方法让我们来处理字符,咱们来看几个常用的

4.1. split(分隔)

split() 方法根据传入的内容将字符串分隔为数组

字符串.split(分隔符)// 返回切割之后的数组

试一试:

  1. 基于提供的数据,测试split方法进行分割`
// 1. 切割为数组
const branchFilter: string = ' 全部分路  | 对抗路 |   中路  | 发育路  | 游走 | 打野 '

const tabFilter: string = '  所有段位  - 巅峰赛1350+ -   顶端排位 - 赛事  '

// 2. 获取日期
const dateStr:string =  '2024-04-27'

4.2. trim(去空格)

trim方法会从字符串的两端移除空白字符,并返回一个【新的字符串】,而不会修改原始字符串。

字符串.trim()// 返回去除空格之后的字符串

试一试:

  1. 使用 trim 去除提供的字符串数据两边的空格
  2. 思考什么时候会用到 trim
// trim 去除两边空格
// 1.基础文本
const str: string = '   123   '
console.log(str.trim()) // 123
// 2.用户名
const username = '  jack  '
// 3.密码
const password = ' 1234abc '

4.3. toLowerCase(转小写) 和 toUpperCase(转大写)

toLowerCase() 方法将该字符串转换为小写形式。toUpperCase() 方法将该字符串转换为大写形式。

字符串.toLowerCase()// 返回转为小写之后的字符串
字符串.toUpperCase()// 返回转为大写之后的字符串

试一试:

  1. 使用 toLowerCase 和 toUpperCase 将提供的字符串转换为 大写或小写
  2. 思考什么时候会用到 toLowerCase 和 toUpperCase?
// toLowerCase 转小写 toUpperCase 转大写
// 1.验证码
const code = '1a2C'
// 2.带后缀名的文件信息
const filename = 'abc.PNG'
// 3.编程语言中的关键字
const boolStr = 'true'

4.4. includes(判断是否存在)

includes() 方法执行区分大小写的搜索,以确定是否可以在一个字符串中找到另一个字符串,并根据情况返回 true 或 false。

字符串.includes(查询的字符串)// 返回判断结果 true / false


试一试:

  1. 使用 includes 对提供的字符串进行判断
  2. 思考什么时候会用到 includes 方法
// includes 方法 判断是否存在特定字符
// 1. 判断是否有@符号
const emailStr = 'user@example.com'

// 2.判断是否为gif文件
const fileName = 'dance.gif'

// 3.判断密码中是否有下划线
const pass = 'itheima-123'

// 4.判断是否有特定词汇 ,比如 牛逼 最 等
const comment = `杭州是一个令人惊叹的地方,我刚刚度过了一个超牛逼的旅行。
西湖的景色太美了,特别是在夕阳西下时,湖水波光粼粼,真是美得不像话。
还去了灵隐寺,感受到了古老的气息和庄严的氛围,让我心生敬意。
在杭州还尝了最正宗的龙井茶和小笼包,老牛逼了!
最喜欢的是游览乌镇古镇,那里的古建筑和石板路让我仿佛穿越回古代。这次旅行真是太棒了,杭州是个值得一游的地方!`

4.5. slice(提取)

slice方法提取字符串的一部分,并将其作为新字符串返回,而不修改原始字符串。

字符串.slice(起始索引)// 从起始索引切割到末尾
字符串.slice(起始索引,结束索引) // 从起始索引,切换到结束索引

试一试:

  1. 使用 slice 方法对提供的字符串进行裁切
// slice方法
// 1. 提取 逗号 之后的内容
const str1 = 'hi,你好吗?'

// 2. 提取 中午
const str2 = '今天中午吃的是西兰花炒蛋'

5. 案例-热度榜单

今天的最后咱们基于目前所学的内容,来完成 热度榜单的例子

在这里插入图片描述
基础模版

@Entry
@Component
struct Page07_RankList {
  build() {
    Column() {
      // 底部区域
      Stack({ alignContent: Alignment.Top }) {
        // 背景图
        Image($r('app.media.ic_bg_517'))
          .height(180)
          .width('100%')

        // 操作栏
        Stack() {
          Row() {
            // 左
            Image($r('app.media.ic_public_arrow_left'))
              .width(24)
              .fillColor(Color.White)
            // 右
            Row({ space: 10 }) {
              Image($r('app.media.ic_public_question'))
                .width(24)
                .fillColor(Color.White)
              Image($r('app.media.ic_public_share'))
                .width(24)
                .fillColor(Color.White)
            }
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)

          // 中间文本
          Column() {
            Row() {
              Text('英雄热度榜')
                .fontColor(Color.White)
              Image($r('app.media.ic_public_arrow_down'))
                .width(20)
                .fillColor(Color.White)
            }

            Row({ space: 5 }) {
              Text('更新时间:xxxx-xx-xx')
                .fontColor(Color.White)
                .fontSize(10)
              Text('算法测试中')
                .fontColor(Color.White)
                .fontSize(9)
                .backgroundColor('37bdee')
                .padding(1)
                .borderRadius({ topLeft: 6, bottomRight: 6 })
            }
          }
          .layoutWeight(1)
        }
        .padding({ top: 10, left: 10, right: 10 })
        .zIndex(2)
        .height(55)
        .backgroundColor(`rgba(0, 0, 0, 0.2)`)
      }

      // 筛选区域
      // 段位筛选 tabFilter
      List({ space: 25 }) {
        ListItem() {
          Stack({ alignContent: Alignment.Bottom }) {
            Text('全部')
              .height(40)
              .padding(10)
              .fontSize(16)
              .fontColor('#aab1b4') //  默认'#aab1b4'/ 高亮 black
            // 底部的横线 高亮时显示,反之隐藏
            // Text()
            //   .height(4)
            //   .width(20)
            //   .borderRadius(2)
            //   .backgroundColor(Color.Orange)
          }
          .height(40)
        }
      }
      .listDirection(Axis.Horizontal)
      .scrollBar(BarState.Off)
      .width('100%')
      .height(50)

      // 英雄列表
      Column() {
        // 顶部区域
        Row() {
          Text('英雄')
            .fontWeight(900)
          Blank()
          Row({ space: 10 }) {
            Text('热度')
              .fontWeight(900)
            Text('胜率')
              .fontWeight(900)
            Text('登场率')
              .fontWeight(900)
            Text('Ban率')
              .fontWeight(900)
          }
        }
        .width('100%')
        .padding(10)

        // 列表区域
        List() {
          ListItem() {
            Row({ space: 14 }) {
              // 头像
              Image($r('app.media.ic_avatar_116'))
                .width(40)
                .borderRadius(10)
              // 昵称+分类
              Column({ space: 5 }) {
                Text('阿珂')
                Text('刺客/打野')
                  .fontSize(10)
                  .fontColor(Color.Gray)
              }
              .width(70)
              .alignItems(HorizontalAlign.Start)

              Blank()
              // 热度 胜率 登场率 Ban 率
              Text('T0')
                .fontWeight(900)
              Text('58.0%')
                .width(45)
                .fontSize(15)
              Text('24.0%')
                .width(45)
                .fontSize(15)
              Text('0.0%')
                .width(45)
                .fontSize(15)
            }
            .width('100%')
          }
          .padding(10)

        }
      }
      .layoutWeight(1)

    }
    .width('100%')
    .height('100%')
  }
}

页面数据

创建 HeroData.ets 文件,参考路径如下:/data/HeroData.ets
在这里插入图片描述

export const branchFilter: string = ' 全部分路   | 对抗路   | 中路  | 发育路   |    游走 | 打野 '

// 英雄数据
export interface Hero {
  banRate: number // ban 率
  showRate: number // 登场率
  winRate: number // 胜率
  tRank: string // 热度
  heroCareer: string // 英雄分类
  heroName: string // 名字
  heroIcon: ResourceStr // 头像
  heroId: number // id
}

// 数组
export const heroList: Hero[] = [
  {
    banRate: 0.403,
    winRate: 0.519,
    showRate: 0.284,
    tRank: 't0',
    heroCareer: '辅助',
    heroId: 505,
    heroIcon: $r('app.media.ic_avatar_505'),
    heroName: '瑶'
  },
  {
    banRate: 0.702,
    winRate: 0.477,
    showRate: 0.097,
    tRank: 't0',
    heroCareer: '辅助',
    heroId: 191,
    heroIcon: $r('app.media.ic_avatar_191'),
    heroName: '大乔'
  },
  {
    banRate: 0.567,
    winRate: 0.487,
    showRate: 0.076,
    tRank: 't0',
    heroCareer: '辅助/坦克',
    heroId: 187,
    heroIcon: $r('app.media.ic_avatar_187'),
    heroName: '东皇太一'
  },
  {
    banRate: 0.452,
    winRate: 0.512,
    showRate: 0.12,
    tRank: 't0',
    heroCareer: '辅助/坦克',
    heroId: 113,
    heroIcon: $r('app.media.ic_avatar_113'),
    heroName: '庄周'
  },
  {
    banRate: 0.356,
    winRate: 0.503,
    showRate: 0.162,
    tRank: 't0',
    heroCareer: '辅助',
    heroId: 184,
    heroIcon: $r('app.media.ic_avatar_184'),
    heroName: '蔡文姬'
  },
  {
    banRate: 0.482,
    winRate: 0.475,
    showRate: 0.082,
    tRank: 't0',
    heroCareer: '辅助',
    heroId: 159,
    heroIcon: $r('app.media.ic_avatar_159'),
    heroName: '朵莉亚'
  },
  {
    banRate: 0.022,
    winRate: 0.495,
    showRate: 0.297,
    tRank: 't0',
    heroCareer: '法师',
    heroId: 142,
    heroIcon: $r('app.media.ic_avatar_142'),
    heroName: '安琪拉'
  },
  {
    banRate: 0.396,
    winRate: 0.496,
    showRate: 0.098,
    tRank: 't0',
    heroCareer: '法师',
    heroId: 563,
    heroIcon: $r('app.media.ic_avatar_563'),
    heroName: '海诺'
  },
]

5.1. 筛选条件渲染

在这里插入图片描述
需求:

  1. 基于提供的数据,渲染出筛选列表(先不考虑高亮切换效果)
    核心步骤:
  2. 导入数据,并分割为数组
  3. 定义组件属性,保存数组,不需要@State修饰
  4. 使用 ForEach 渲染列表,结合 trim 处理两侧空格
import { branchFilter } from '../data/HeroData'

@Entry
@Component
struct Page07_RankList {
  // 分路数组
  branchList: string [] = branchFilter.split('|')

  build() {
    Column() {
      // 底部区域
      Stack({ alignContent: Alignment.Top }) {
        // 背景图
        Image($r('app.media.ic_bg_517'))
          .height(180)
          .width('100%')

        // 操作栏
        Stack() {
          Row() {
            // 左
            Image($r('app.media.ic_public_arrow_left'))
              .width(24)
              .fillColor(Color.White)
            // 右
            Row({ space: 10 }) {
              Image($r('app.media.ic_public_question'))
                .width(24)
                .fillColor(Color.White)
              Image($r('app.media.ic_public_share'))
                .width(24)
                .fillColor(Color.White)
            }
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)

          // 中间文本
          Column() {
            Row() {
              Text('英雄热度榜')
                .fontColor(Color.White)
              Image($r('app.media.ic_public_arrow_down'))
                .width(20)
                .fillColor(Color.White)
            }

            Row({ space: 5 }) {
              Text('更新时间:xxxx-xx-xx')
                .fontColor(Color.White)
                .fontSize(10)
              Text('算法测试中')
                .fontColor(Color.White)
                .fontSize(9)
                .backgroundColor('37bdee')
                .padding(1)
                .borderRadius({ topLeft: 6, bottomRight: 6 })
            }
          }
          .layoutWeight(1)
        }
        .padding({ top: 10, left: 10, right: 10 })
        .zIndex(2)
        .height(55)
        .backgroundColor(`rgba(0, 0, 0, 0.2)`)
      }

      // 筛选区域
      // 段位筛选 tabFilter
      List({ space: 25 }) {
        ForEach(this.branchList, (item: string, index: number) => {
          ListItem() {
            Stack({ alignContent: Alignment.Bottom }) {
              Text(item.trim())
                .height(40)
                .padding(10)
                .fontSize(16)
                .fontColor('#aab1b4') //  默认'#aab1b4'/ 高亮 black
              // 底部的横线 高亮时显示,反之隐藏
              // Text()
              //   .height(4)
              //   .width(20)
              //   .borderRadius(2)
              //   .backgroundColor(Color.Orange)
            }
            .height(40)
          }
        })

      }
      .listDirection(Axis.Horizontal)
      .scrollBar(BarState.Off)
      .width('100%')
      .height(50)

      // 英雄列表
      Column() {
        // 顶部区域
        Row() {
          Text('英雄')
            .fontWeight(900)
          Blank()
          Row({ space: 10 }) {
            Text('热度')
              .fontWeight(900)
            Text('胜率')
              .fontWeight(900)
            Text('登场率')
              .fontWeight(900)
            Text('Ban率')
              .fontWeight(900)
          }
        }
        .width('100%')
        .padding(10)

        // 列表区域
        List() {
          ListItem() {
            Row({ space: 14 }) {
              // 头像
              Image($r('app.media.ic_avatar_116'))
                .width(40)
                .borderRadius(10)
              // 昵称+分类
              Column({ space: 5 }) {
                Text('阿珂')
                Text('刺客/打野')
                  .fontSize(10)
                  .fontColor(Color.Gray)
              }
              .width(70)
              .alignItems(HorizontalAlign.Start)

              Blank()
              // 热度 胜率 登场率 Ban 率
              Text('T0')
                .fontWeight(900)
              Text('58.0%')
                .width(45)
                .fontSize(15)
              Text('24.0%')
                .width(45)
                .fontSize(15)
              Text('0.0%')
                .width(45)
                .fontSize(15)
            }
            .width('100%')
          }
          .padding(10)

        }
      }
      .layoutWeight(1)

    }
    .width('100%')
    .height('100%')
  }
}

5.2. 高亮切换

接下来完成高亮的切换效果

在这里插入图片描述
需求:

  1. 点击分路信息,切换高亮效果
    核心步骤:
  2. 定义状态变量,记录当前高亮索引 ,需要用@State 修饰
  3. 点击记录当前点击索引
  4. 根据当前索引值,调整列表项的高亮效果
import { branchFilter } from '../data/HeroData'

@Entry
@Component
struct Page07_RankList {
  // 分路数组
  branchList: string [] = branchFilter.split('|')
  // 选中的筛选条件
  @State selectedIndex: number = 0

  build() {
    Column() {
      // 底部区域
      Stack({ alignContent: Alignment.Top }) {
        // 背景图
        Image($r('app.media.ic_bg_517'))
          .height(180)
          .width('100%')

        // 操作栏
        Stack() {
          Row() {
            // 左
            Image($r('app.media.ic_public_arrow_left'))
              .width(24)
              .fillColor(Color.White)
            // 右
            Row({ space: 10 }) {
              Image($r('app.media.ic_public_question'))
                .width(24)
                .fillColor(Color.White)
              Image($r('app.media.ic_public_share'))
                .width(24)
                .fillColor(Color.White)
            }
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)

          // 中间文本
          Column() {
            Row() {
              Text('英雄热度榜')
                .fontColor(Color.White)
              Image($r('app.media.ic_public_arrow_down'))
                .width(20)
                .fillColor(Color.White)
            }

            Row({ space: 5 }) {
              Text('更新时间:xxxx-xx-xx')
                .fontColor(Color.White)
                .fontSize(10)
              Text('算法测试中')
                .fontColor(Color.White)
                .fontSize(9)
                .backgroundColor('37bdee')
                .padding(1)
                .borderRadius({ topLeft: 6, bottomRight: 6 })
            }
          }
          .layoutWeight(1)
        }
        .padding({ top: 10, left: 10, right: 10 })
        .zIndex(2)
        .height(55)
        .backgroundColor(`rgba(0, 0, 0, 0.2)`)
      }

      // 筛选区域
      // 段位筛选 tabFilter
      List({ space: 25 }) {
        ForEach(this.branchList, (item: string, index: number) => {
          ListItem() {
            Stack({ alignContent: Alignment.Bottom }) {
              Text(item.trim())
                .height(40)
                .padding(10)
                .fontSize(16)
                .fontColor(this.selectedIndex == index ? Color.Black : '#aab1b4') //  默认'#aab1b4'/ 高亮 black
              // 底部的横线 高亮时显示,反之隐藏
              if (this.selectedIndex == index) {
                Text()
                  .height(4)
                  .width(20)
                  .borderRadius(2)
                  .backgroundColor(Color.Orange)
              }
            }
            .height(40)
          }
          .onClick(() => {
            this.selectedIndex = index
          })
        })

      }
      .listDirection(Axis.Horizontal)
      .scrollBar(BarState.Off)
      .width('100%')
      .height(50)

      // 英雄列表
      Column() {
        // 顶部区域
        Row() {
          Text('英雄')
            .fontWeight(900)
          Blank()
          Row({ space: 10 }) {
            Text('热度')
              .fontWeight(900)
            Text('胜率')
              .fontWeight(900)
            Text('登场率')
              .fontWeight(900)
            Text('Ban率')
              .fontWeight(900)
          }
        }
        .width('100%')
        .padding(10)

        // 列表区域
        List() {
          ListItem() {
            Row({ space: 14 }) {
              // 头像
              Image($r('app.media.ic_avatar_116'))
                .width(40)
                .borderRadius(10)
              // 昵称+分类
              Column({ space: 5 }) {
                Text('阿珂')
                Text('刺客/打野')
                  .fontSize(10)
                  .fontColor(Color.Gray)
              }
              .width(70)
              .alignItems(HorizontalAlign.Start)

              Blank()
              // 热度 胜率 登场率 Ban 率
              Text('T0')
                .fontWeight(900)
              Text('58.0%')
                .width(45)
                .fontSize(15)
              Text('24.0%')
                .width(45)
                .fontSize(15)
              Text('0.0%')
                .width(45)
                .fontSize(15)
            }
            .width('100%')
          }
          .padding(10)

        }
      }
      .layoutWeight(1)

    }
    .width('100%')
    .height('100%')
  }
}

5.3. 列表渲染

最后咱们来完成列表的渲染

在这里插入图片描述
需求:

  1. 将提供的列表数据渲染到页面上
  2. 今天【不考虑筛选】的逻辑,筛选的逻辑等学习了【数组的相关】方法再来完成
    核心步骤:
  3. 导入列表数据
  4. 定义为状态变量,需要用@State 修饰,后续要筛选
  5. 通过 ForEach渲染到页面上
import { branchFilter, Hero, heroList } from '../data/HeroData'

@Entry
@Component
struct Page07_RankList {
  // 分路数组
  branchList: string [] = branchFilter.split('|')
  // 选中的筛选条件
  @State selectedIndex: number = 0
  // 渲染列表
  @State heroList: Hero[] = heroList

  // 小数转为百分数
  getPercent(num: number) {
    return (num * 100).toFixed(1) + '%'
  }

  build() {
    Column() {
      // 底部区域
      Stack({ alignContent: Alignment.Top }) {
        // 背景图
        Image($r('app.media.ic_bg_517'))
          .height(180)
          .width('100%')

        // 操作栏
        Stack() {
          Row() {
            // 左
            Image($r('app.media.ic_public_arrow_left'))
              .width(24)
              .fillColor(Color.White)
            // 右
            Row({ space: 10 }) {
              Image($r('app.media.ic_public_question'))
                .width(24)
                .fillColor(Color.White)
              Image($r('app.media.ic_public_share'))
                .width(24)
                .fillColor(Color.White)
            }
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)

          // 中间文本
          Column() {
            Row() {
              Text('英雄热度榜')
                .fontColor(Color.White)
              Image($r('app.media.ic_public_arrow_down'))
                .width(20)
                .fillColor(Color.White)
            }

            Row({ space: 5 }) {
              Text('更新时间:xxxx-xx-xx')
                .fontColor(Color.White)
                .fontSize(10)
              Text('算法测试中')
                .fontColor(Color.White)
                .fontSize(9)
                .backgroundColor('37bdee')
                .padding(1)
                .borderRadius({ topLeft: 6, bottomRight: 6 })
            }
          }
          .layoutWeight(1)
        }
        .padding({ top: 10, left: 10, right: 10 })
        .zIndex(2)
        .height(55)
        .backgroundColor(`rgba(0, 0, 0, 0.2)`)
      }

      // 筛选区域
      // 段位筛选 tabFilter
      List({ space: 25 }) {
        ForEach(this.branchList, (item: string, index: number) => {
          ListItem() {
            Stack({ alignContent: Alignment.Bottom }) {
              Text(item.trim())
                .height(40)
                .padding(10)
                .fontSize(16)
                .fontColor(this.selectedIndex == index ? Color.Black : '#aab1b4') //  默认'#aab1b4'/ 高亮 black
              // 底部的横线 高亮时显示,反之隐藏
              if (this.selectedIndex == index) {
                Text()
                  .height(4)
                  .width(20)
                  .borderRadius(2)
                  .backgroundColor(Color.Orange)
              }
            }
            .height(40)
          }
          .onClick(() => {
            this.selectedIndex = index
          })
        })

      }
      .listDirection(Axis.Horizontal)
      .scrollBar(BarState.Off)
      .width('100%')
      .height(50)

      // 英雄列表
      Column() {
        // 顶部区域
        Row() {
          Text('英雄')
            .fontWeight(900)
          Blank()
          Row({ space: 10 }) {
            Text('热度')
              .fontWeight(900)
            Text('胜率')
              .fontWeight(900)
            Text('登场率')
              .fontWeight(900)
            Text('Ban率')
              .fontWeight(900)
          }
        }
        .width('100%')
        .padding(10)

        // 列表区域
        List() {
          ForEach(this.heroList, (item: Hero, index: number) => {
            ListItem() {
              Row({ space: 14 }) {
                // 头像
                Image(item.heroIcon)
                  .width(40)
                  .borderRadius(10)
                // 昵称+分类
                Column({ space: 5 }) {
                  Text(item.heroName)
                  Text(item.heroCareer)
                    .fontSize(10)
                    .fontColor(Color.Gray)
                }
                .width(70)
                .alignItems(HorizontalAlign.Start)

                Blank()
                // 热度 胜率 登场率 Ban 率
                Text(item.tRank.toUpperCase())
                  .fontWeight(900)
                Text(this.getPercent(item.winRate))// 胜率
                  .width(45)
                  .fontSize(15)
                Text(this.getPercent(item.showRate))// 登场率
                  .width(45)
                  .fontSize(15)
                Text(this.getPercent(item.banRate))// Ban 率
                  .width(45)
                  .fontSize(15)
              }
              .width('100%')
            }
            .padding(10)
          })

        }
      }
      .layoutWeight(1)

    }
    .width('100%')
    .height('100%')
  }
}

5.4. 渲染日期

最后完成渲染日期的功能

在这里插入图片描述
需求:

  1. 页面顶部渲染当前日期(年/月/日)
    核心步骤:
  2. 通过 Date 获取当前日期并渲染到页面上
import { branchFilter, Hero, heroList } from '../data/HeroData'

@Entry
@Component
struct Page07_RankList {
  // 分路数组
  branchList: string [] = branchFilter.split('|')
  // 选中的筛选条件
  @State selectedIndex: number = 0
  // 渲染列表
  @State heroList: Hero[] = heroList
  // 当前时间
  today: string = this.getTody()

  // 计算当前日期
  getTody() {
    // 计算当前日期
    const date = new Date()
    // 年月日
    const year = date.getFullYear()
    const month = date.getMonth() + 1
    const day = date.getDate()
    return `${year}/${month}/${day}`
  }

  // 小数转为百分数
  getPercent(num: number) {
    return (num * 100).toFixed(1) + '%'
  }

  build() {
    Column() {
      // 底部区域
      Stack({ alignContent: Alignment.Top }) {
        // 背景图
        Image($r('app.media.ic_bg_517'))
          .height(180)
          .width('100%')

        // 操作栏
        Stack() {
          Row() {
            // 左
            Image($r('app.media.ic_public_arrow_left'))
              .width(24)
              .fillColor(Color.White)
            // 右
            Row({ space: 10 }) {
              Image($r('app.media.ic_public_question'))
                .width(24)
                .fillColor(Color.White)
              Image($r('app.media.ic_public_share'))
                .width(24)
                .fillColor(Color.White)
            }
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)

          // 中间文本
          Column() {
            Row() {
              Text('英雄热度榜')
                .fontColor(Color.White)
              Image($r('app.media.ic_public_arrow_down'))
                .width(20)
                .fillColor(Color.White)
            }

            Row({ space: 5 }) {
              Text('更新时间:'+this.today)
                .fontColor(Color.White)
                .fontSize(10)
              Text('算法测试中')
                .fontColor(Color.White)
                .fontSize(9)
                .backgroundColor('37bdee')
                .padding(1)
                .borderRadius({ topLeft: 6, bottomRight: 6 })
            }
          }
          .layoutWeight(1)
        }
        .padding({ top: 10, left: 10, right: 10 })
        .zIndex(2)
        .height(55)
        .backgroundColor(`rgba(0, 0, 0, 0.2)`)
      }

      // 筛选区域
      // 段位筛选 tabFilter
      List({ space: 25 }) {
        ForEach(this.branchList, (item: string, index: number) => {
          ListItem() {
            Stack({ alignContent: Alignment.Bottom }) {
              Text(item.trim())
                .height(40)
                .padding(10)
                .fontSize(16)
                .fontColor(this.selectedIndex == index ? Color.Black : '#aab1b4') //  默认'#aab1b4'/ 高亮 black
              // 底部的横线 高亮时显示,反之隐藏
              if (this.selectedIndex == index) {
                Text()
                  .height(4)
                  .width(20)
                  .borderRadius(2)
                  .backgroundColor(Color.Orange)
              }
            }
            .height(40)
          }
          .onClick(() => {
            this.selectedIndex = index
          })
        })

      }
      .listDirection(Axis.Horizontal)
      .scrollBar(BarState.Off)
      .width('100%')
      .height(50)

      // 英雄列表
      Column() {
        // 顶部区域
        Row() {
          Text('英雄')
            .fontWeight(900)
          Blank()
          Row({ space: 10 }) {
            Text('热度')
              .fontWeight(900)
            Text('胜率')
              .fontWeight(900)
            Text('登场率')
              .fontWeight(900)
            Text('Ban率')
              .fontWeight(900)
          }
        }
        .width('100%')
        .padding(10)

        // 列表区域
        List() {
          ForEach(this.heroList, (item: Hero, index: number) => {
            ListItem() {
              Row({ space: 14 }) {
                // 头像
                Image(item.heroIcon)
                  .width(40)
                  .borderRadius(10)
                // 昵称+分类
                Column({ space: 5 }) {
                  Text(item.heroName)
                  Text(item.heroCareer)
                    .fontSize(10)
                    .fontColor(Color.Gray)
                }
                .width(70)
                .alignItems(HorizontalAlign.Start)

                Blank()
                // 热度 胜率 登场率 Ban 率
                Text(item.tRank.toUpperCase())
                  .fontWeight(900)
                Text(this.getPercent(item.winRate))// 胜率
                  .width(45)
                  .fontSize(15)
                Text(this.getPercent(item.showRate))// 登场率
                  .width(45)
                  .fontSize(15)
                Text(this.getPercent(item.banRate))// Ban 率
                  .width(45)
                  .fontSize(15)
              }
              .width('100%')
            }
            .padding(10)
          })

        }
      }
      .layoutWeight(1)

    }
    .width('100%')
    .height('100%')
  }
}
  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值