定时器、递归、Math、Date
今日核心:
- 定时器、递归、内置对象、常用方法
1. 定时器
日常开发中如果需要代码 延迟一会执行,或者每隔一段时间执行一次,就可以使用定时器
定时器有两种:
- setTimeout: 延迟执行
- 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)
}
}
试一试:
- 延迟执行逻辑
- 获取返回的 定时器 id
- 通过定时器 id 清除 已开启的定时器
tips: - 定时器中传入的函数虽然是延迟执行,但是并【不会阻塞】其他的代码
@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)
试一试:
- 间隔执行逻辑
- 获取返回的 定时器 id
- 通过定时器 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. 案例-获取验证码
接下来通过刚刚学习的定时器,来完成一个获取验证码的倒计时功能
需求:
- 获取验证码
a. 点击按钮之后,延迟 2 秒(setTimeout)
b. 获取一个 4 位的随机数,通过弹框显示出来(AlertDialog.show({message:}))
c. Math.floor(1000+Math.random()*9000) - 倒计时效果
a. 点击开启倒计时
ⅰ. @State time = 0- 点了之后->60
ⅱ. setInterval - time–
- time==0 关闭定时器
b. 切换显示效果
ⅰ. 默认:发送验证码
ⅱ. 倒计时中:xx 秒后获取
ⅲ. 根据 time 是否为 0 来决定 - 0:发送验证码
- 不为 0:xx 秒后获取
c. 倒计时中无法重复点击
ⅰ. 时间为 0 时:可以点击
ⅱ. 时间不为 0 时:不可以点击
分析:
- 点了之后->60
@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()
}
试一试:
- 完成函数剩余部分内容
- 使用递归完成
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函数,获取公司所有部门的总人数
核心步骤:
- 先求和最外层
- 逐层求和,找到规律
- 抽取
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. 案例-时钟
结合刚刚学习的内容,来完成一个时钟案例
需求:
- 开屏显示
a. 默认显示当前的 年月日
b. 星期
c. 时分秒 - 倒计时
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(分隔符)// 返回切割之后的数组
试一试:
- 基于提供的数据,测试split方法进行分割`
// 1. 切割为数组
const branchFilter: string = ' 全部分路 | 对抗路 | 中路 | 发育路 | 游走 | 打野 '
const tabFilter: string = ' 所有段位 - 巅峰赛1350+ - 顶端排位 - 赛事 '
// 2. 获取日期
const dateStr:string = '2024-04-27'
4.2. trim(去空格)
trim方法会从字符串的两端移除空白字符,并返回一个【新的字符串】,而不会修改原始字符串。
字符串.trim()// 返回去除空格之后的字符串
试一试:
- 使用 trim 去除提供的字符串数据两边的空格
- 思考什么时候会用到 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()// 返回转为大写之后的字符串
试一试:
- 使用 toLowerCase 和 toUpperCase 将提供的字符串转换为 大写或小写
- 思考什么时候会用到 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
试一试:
- 使用 includes 对提供的字符串进行判断
- 思考什么时候会用到 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(起始索引,结束索引) // 从起始索引,切换到结束索引
试一试:
- 使用 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. 筛选条件渲染
需求:
- 基于提供的数据,渲染出筛选列表(先不考虑高亮切换效果)
核心步骤: - 导入数据,并分割为数组
- 定义组件属性,保存数组,不需要@State修饰
- 使用 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. 高亮切换
接下来完成高亮的切换效果
需求:
- 点击分路信息,切换高亮效果
核心步骤: - 定义状态变量,记录当前高亮索引 ,需要用@State 修饰
- 点击记录当前点击索引
- 根据当前索引值,调整列表项的高亮效果
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. 列表渲染
最后咱们来完成列表的渲染
需求:
- 将提供的列表数据渲染到页面上
- 今天【不考虑筛选】的逻辑,筛选的逻辑等学习了【数组的相关】方法再来完成
核心步骤: - 导入列表数据
- 定义为状态变量,需要用@State 修饰,后续要筛选
- 通过 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. 渲染日期
最后完成渲染日期的功能
需求:
- 页面顶部渲染当前日期(年/月/日)
核心步骤: - 通过 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%')
}
}