目录
十一.模态转场、List 组件、AlphabetIndexer
十一.模态转场、List 组件、AlphabetIndexer
今日核心:
- 容器组件:模态\半模态转场、list 组件进阶、AlphabetIndexer 组件
1. 模态转场
今天首先来学习模态转场,就是页面中弹出,全屏&半屏的弹框:
1.1. 半模态转场
1.1.1. 核心用法
半模态顾名思义,就是半屏的模态效果(图 2)
名称 | 参数 | 参数描述 |
bindSheet | isShow: boolean, builder: CustomBuilder, options?: SheetOptions | 给组件绑定半模态页面,点击后显示模态页面。 isShow: 是否显示半模态页面。 从API version 10开始,该参数支持$$双向绑定变量 builder: 配置半模态页面内容。 options: 配置半模态页面的可选属性。 |
核心步骤:
- 定义状态变量,boolean
- 通过 Builder 定义结构
- 绑定半模态 bindSheet
- 修改状态变量,控制显示
参考代码:
@Entry
@Component
struct Page01_bindSheet {
// 1. 定义状态变量,boolean
@State isShow: boolean = false
build() {
Column() {
Text('半模态转场')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Button('显示半模态')
// 3. 绑定半模态 bindSheet
.bindSheet(this.isShow, this.sheetBuilder())
.onClick(() => {
// 4. 修改状态变量,控制显示
this.isShow = true
})
}
.width('100%')
.height('100%')
.padding(20)
}
// 2.通过 Builder 定义结构
@Builder
sheetBuilder() {
Column() {
}
.backgroundColor(Color.Pink)
.width('100%')
.height('100%')
}
}
1.1.2. 显示状态双向绑定
上一节的代码有个小问题,如果通过那个关闭按钮来关闭模态会出现无法开启的情况
官方给出了解决方案:
说明
在非双向绑定情况下,以拖拽方式关闭半模态页面不会改变isShow参数的值。
为了使isShow参数值与半模态界面的状态同步,建议使用$$双向绑定isShow参数。
HarmonyOs DevEco Studio小技巧7--双向绑定报错怎么解决_ecodev 怎么双向绑定类字段-CSDN博客
// 其余代码略
.bindSheet($$this.isShow, this.sheetBuilder())
名称 | 参数 | 参数描述 |
bindSheet | isShow: boolean, builder: CustomBuilder, options?: SheetOptions | 给组件绑定半模态页面,点击后显示模态页面。 isShow: 是否显示半模态页面。 从API version 10开始,该参数支持$$双向绑定变量 builder: 配置半模态页面内容。 options: 配置半模态页面的可选属性。 |
参考代码:
@Entry
@Component
struct Page02_bindSheet_TwoWay {
// 1. 定义状态变量,boolean
@State isShow: boolean = false
build() {
Column() {
Text('半模态转场' + this.isShow)
.fontSize(20)
.fontWeight(FontWeight.Bold)
Button('显示半模态')// 3. 绑定半模态 bindSheet
.bindSheet($$this.isShow, this.sheetBuilder())
.onClick(() => {
// 4. 修改状态变量,控制显示
this.isShow = true
})
}
.width('100%')
.height('100%')
.padding(20)
}
// 2.通过 Builder 定义结构
@Builder
sheetBuilder() {
Column() {
}
.backgroundColor(Color.Pink)
.width('100%')
.height('100%')
}
}
双向绑定示意图:
- 半模态拖拽关闭,以及点击 x 关闭时均会修改isShow的值为 false
注意:
- 有不少组件也支持双向绑定的组件,也可以使用语法$$进行进行绑定
1.1.3. 可选属性sheetOptions
通过第三个可选参数SheetOptions,可以对半模态的内容进行调整
其中常用属性已经标红
名称 | 类型 | 必填 | 描述 |
height | | Length | 否 | 半模态高度,默认是LARGE。 说明: 底部弹窗竖屏时,当设置detents时,该属性设置无效。 |
dragBar | boolean | 否 | 是否显示控制条,默认显示。 说明: 半模态面板的dentents属性设置多个不同高度并且设置生效时,默认显示控制条。否则不显示控制条。 |
showClose | boolean | Resource | 否 | 是否显示关闭图标,默认显示。 |
detents | [(SheetSize | Length), ( SheetSize | Length)?, (SheetSize | Length)?] | 否 | 半模态页面的切换高度档位。 说明: 底部弹窗竖屏生效,元组中第一个高度为初始高度。 |
。。。。剩余属性参考文档 |
试一试:
- 效果 1:弹出半模态,高度固定
- 效果 2:弹出半模态,通过控制条切换高度档位
基础模版:
@Entry
@Component
struct Page01_bindSheet {
@State isShow: boolean = true
build() {
Button('显示半模态')
.bindSheet($$this.isShow, this.sheetBuilder())
}
@Builder
sheetBuilder() {
Text('我是弹出来的内容')
.backgroundColor(Color.Pink)
}
}
参考代码:
@Entry
@Component
struct Page01_bindSheet {
@State isShow: boolean = true
build() {
Button('显示半模态')
.bindSheet($$this.isShow, this.sheetBuilder(), {
height: SheetSize.LARGE, // 设置弹底式窗口的高度为大
// height: SheetSize.MEDIUM, // 设置弹底式窗口的高度为中
// height: SheetSize.FIT_CONTENT, // 设置弹底式窗口的高度为根据内容自适应
detents: [SheetSize.LARGE, SheetSize.MEDIUM, SheetSize.FIT_CONTENT], // 设置弹底式窗口的停靠点高度,包括大、中和根据内容自适应
// 💥dragBar需要和档位detents配合使用, 否则不显示
dragBar: false, // 禁用弹底式窗口的拖拽栏
showClose: false // 禁用显示关闭按钮
})
}
// 通过 Builder 定义结构
@Builder
sheetBuilder() {
Text('我是弹出来的内容')
.backgroundColor(Color.Pink)
}
}
1.2. 案例-分享弹框
需求:
- 将提供的默认结构提取到 Builder 中
- 设置模态框
- 高度为 适应内容
- 不使用半模态的关闭按钮
- 使用自己的 x 关闭半模态
核心步骤:
- 将提供的默认结构提取到 Builder 中
- 选中要提取的结构,右键
- 设置模态框
- 定义状态变量
- 绑定半模态 bindSheet,设置布尔值,builder,选项
- 选项:
- 高度适应内容
- 不显示默认关闭按钮
- 使用自己的 x 关闭半模态
- 绑定点击事件,修改状态变量
1.2.1. 基础模版:
@Entry
@Component
struct Page04_bindSheetDemo_Share {
// 半模态 内部图片 Builder
@Builder
itemBuilder(src: ResourceStr, title: string) {
Column() {
Image(src)
.width(50)
.height(50)
Text(title)
.fontSize(14)
.margin({ top: 10 })
}
}
build() {
Column() {
Button(`显示分享半模态`)
.fontSize(20)
// 半模态的内容
Column() {
// 顶部区域
Stack({ alignContent: Alignment.End }) {
Text('分享给好友')
.width('100%')
.textAlign(TextAlign.Center)
.fontSize(20)
// 自定义的关闭
Image($r('app.media.ic_public_cancel'))
.width(25)
.fillColor('#c0c0c0')
}
.width('100%')
// 底部的列表
Row() {
this.itemBuilder($r('app.media.ic_share_sina'), '微信')
this.itemBuilder($r('app.media.ic_share_url'), '朋友圈')
this.itemBuilder($r('app.media.ic_share_wechat'), '微博')
this.itemBuilder($r('app.media.ic_share_pyq'), '复制链接')
}
.width('100%')
.margin({ top: 30 })
.justifyContent(FlexAlign.SpaceAround)
}
.padding(15)
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
}
}
1.2.2. 参考代码:
@Entry
@Component
struct Page04_bindSheetDemo_Share {
@State isShow: boolean = false
// 半模态 内部图片 Builder
@Builder
itemBuilder(src: ResourceStr, title: string) {
Column() {
Image(src)
.width(50)
.height(50)
Text(title)
.fontSize(14)
.margin({ top: 10 })
}
}
build() {
Column() {
Button(`显示分享半模态`)
.fontSize(20)
.bindSheet($$this.isShow, this.sheetBuilder(), {
height: SheetSize.FIT_CONTENT, // 适应内容高度
showClose: false // 不显示默认的关闭按钮
})
.onClick(() => {
this.isShow = true
})
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
}
@Builder
sheetBuilder() {
// 半模态的内容
Column() {
// 顶部区域
Stack({ alignContent: Alignment.End }) {
Text('分享给好友')
.width('100%')
.textAlign(TextAlign.Center)
.fontSize(20)
// 自定义的关闭
Image($r('app.media.ic_public_cancel'))
.width(25)
.fillColor('#c0c0c0')
.onClick(() => {
this.isShow = false
})
}
.width('100%')
// 底部的列表
Row() {
this.itemBuilder($r('app.media.ic_share_sina'), '微信')
this.itemBuilder($r('app.media.ic_share_url'), '朋友圈')
this.itemBuilder($r('app.media.ic_share_wechat'), '微博')
this.itemBuilder($r('app.media.ic_share_pyq'), '复制链接')
}
.width('100%')
.margin({ top: 30 })
.justifyContent(FlexAlign.SpaceAround)
}
.padding(15)
}
}
注意:
- 半模态中的图片如果不设置高度,默认不会加载,导致半模态默认不显示图片
- 解决方法:给图片或图片的容器加个高度即可
1.3. 全屏模态
名称 | 参数 | 参数描述 |
bindContentCover | isShow: boolean, builder: CustomBuilder, options?: ContentCoverOptions | 给组件绑定全屏模态页面,点击后显示模态页面。模态页面内容自定义,显示方式可设置无动画过渡,上下切换过渡以及透明渐变过渡方式。 isShow: 是否显示全屏模态页面。 从API version 10开始,该参数支持$$双向绑定变量 builder: 配置全屏模态页面内容。 options: 配置全屏模态页面的可选属性(详细可以参考文档,用的不多) |
核心步骤:
- 定义状态变量,boolean
- 通过 Builder 定义结构
- 绑定全屏模态 bindContentCover
- 修改状态变量,控制显示
@Entry
@Component
struct Page05_bindContentCover {
// 1. 定义状态变量,boolean
@State isShow: boolean = false
// 2.通过 Builder 定义结构
@Builder
myBuilder() {
Column() {
Button('关闭')
.onClick(() => {
this.isShow = false
})
}
.width('100%')
.height('100%')
.backgroundColor('#0094ff')
.justifyContent(FlexAlign.Center)
}
build() {
Column() {
Button(`显示全屏模态${this.isShow}`)
.fontSize(20)
.margin(10)// 绑定全屏模态 bindContentCover
.bindContentCover(this.isShow, this.myBuilder())
.onClick(() => {
// 修改状态变量,控制显示
this.isShow = true
})
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
}
}
2. List组件
List 组件的基本用法,可以用它来展示列表,并且实现列表滚动,日常开发的时候还可以用它来实现更为复杂的效果。
列表是一种复杂的容器,当列表项达到一定数量,超过List容器组件大小时,可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集,例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求(如通讯录、音乐列表、购物清单等)。
List 表示列表容器,ListItem表示单个列表项,可以包含单个子组件。
2.1. 基本使用
List() {
listItem() {}
.....
}
示例代码
@Entry
@Component
struct Index {
build() {
Column() {
List() {
ListItem() {
Text('文字')
.width("100%")
.height(40)
.backgroundColor(Color.Pink)
}
}
.width('100%')
.height(200)
.backgroundColor('#ccc')
}
}
}
2.2. 常用属性
2.2.1. 滚动条状态
属性:scrollBar()
参数:枚举 BarState
- Off: 不显示
- On:常驻显示
- Auto:按需显示(触摸时显示,2s 后消失)
List() {
listItem()
......
}
.scrollBar(BarState.Off)
2.2.2. 分割线样式
列表默认没有分割线
属性:divider()
参数:{ strokeWidth: 数字, color?: 'color', startMargin?: 数字, endMargin?: 数字 }
List() {
ListItem()
......
}
.divider({
strokeWidth: 1, // 设置分割线的宽度为1
color: Color.Orange, // 设置分割线的颜色为橙色
startMargin: 10, // 设置分割线起点的边距为10
endMargin: 10 // 设置分割线终点的边距为10
})
2.3. 分组展示
核心用法:
- List作为顶级容器
- ListItemGroup 作为分组容器
- ListItem作为 List 或者ListItemGroup的子组件
ListItemGroup组件参数,以对象形式传入
ListItemGroup(参数){}.属性()
参数名 | 参数类型 | 必填 | 参数描述 |
header | 否 | 设置ListItemGroup头部组件。 | |
footer | 否 | 设置ListItemGroup尾部组件。 | |
space | number | string | 否 | 列表项间距。只作用于ListItem与ListItem之间,不作用于header与ListItem、footer与ListItem之间。 |
ListItemGroup组件属性,和 List 是一样的
名称 | 参数类型 | 描述 |
divider | { strokeWidth: Length, color?: ResourceColor, startMargin?: Length, endMargin?: Length } | null | 用于设置ListItem分割线样式,默认无分割线。 strokeWidth: 分割线的线宽。 color: 分割线的颜色。 startMargin: 分割线距离列表侧边起始端的距离。 endMargin: 分割线距离列表侧边结束端的距离。 strokeWidth, startMargin和endMargin不支持设置百分比。 |
@Entry
@Component
struct ListItemGroup_01 {
@Builder
headerBuilder() {
Text('我是头部')
}
@Builder
footerBuilder() {
Text('我是底部')
}
build() {
List() {
ListItemGroup({
header: this.headerBuilder(),
footer: this.footerBuilder(),
space: 20
}) {
ListItem() {
Text('我是内容')
.backgroundColor(Color.Orange)
}
ListItem() {
Text('我是内容')
.backgroundColor(Color.Orange)
}
}
.divider({ strokeWidth: 1, color: Color.Orange,startMargin:40 })
}
}
}
试一试:
- 测试参数及属性的使用
- 实现如下效果
参考代码:
@Entry
@Component
struct ContactsList {
build() {
List() {
ListItemGroup({ header: this.itemHead('A'), space: 20 }) {
// 循环渲染分组A的ListItem
this.contactBuilder('艾佳')
this.contactBuilder('安安')
this.contactBuilder('艾米丽')
}
.divider({
startMargin: 60,
strokeWidth: 1,
color: '#ccc'
})
ListItemGroup({ header: this.itemHead('B'), space: 20 }) {
// 循环渲染分组B的ListItem
this.contactBuilder('白客')
this.contactBuilder('白夜')
this.contactBuilder('博明')
}
.divider({
startMargin: 60,
strokeWidth: 1,
color: '#ccc'
})
}
}
@Builder
itemHead(text: string) {
// 列表分组的头部组件,对应联系人分组A、B等位置的组件
Text(text)
.fontSize(20)
.backgroundColor('#fff1f3f5')
.width('100%')
.padding(5)
}
@Builder
contactBuilder(name: string) {
ListItem() {
Row({ space: 10 }) {
Image($r('app.media.ic_public_lianxiren'))
.width(40)
.fillColor('#e4b99a')
Text(name)
}
}
}
}
2.4. 案例-通讯录
List 组件一般基于数据渲染出列表,咱们基于数据来渲染出完整的列表,并且设置随机头像颜色
需求:
- 基于数据渲染列表
- 随机头像颜色
2.4.1. 需求 1-数据渲染
思考:
- 数据长什么样子?
基础模版:
interface ContactContent {
initial: string
nameList: string[]
}
@Entry
@Component
struct Page07_ListDemo_Contact {
contacts: ContactContent[] = [
{ initial: 'A', nameList: ['阿猫', '阿狗', '阿虎', '阿龙', '阿鹰', '阿狼', '阿豹', '阿狮', '阿象', '阿鲸'] },
{ initial: 'B', nameList: ['白兔', '白鸽', '白鹤', '白鹭', '白狐', '白狼', '白虎', '白鹿', '白蛇', '白马'] },
{ initial: 'C', nameList: ['春花', '春风', '春雨', '春草', '春柳', '春燕', '春莺', '春蝶', '春蓝', '春绿'] },
{ initial: 'D', nameList: ['冬雪', '冬梅', '冬松', '冬竹', '冬云', '冬霜', '冬月', '冬夜', '冬青', '冬红'] },
{ initial: 'E', nameList: ['饿狼', '饿虎', '饿鹰', '饿豹', '饿熊', '饿蛇', '饿鱼', '饿虾', '饿蟹', '饿蚌'] },
{ initial: 'F', nameList: ['飞鸟', '飞鱼', '飞虫', '飞蜂', '飞蝶', '飞蛾', '飞蝉', '飞蝗', '飞鼠', '飞猫'] },
{ initial: 'G', nameList: ['孤狼', '孤鹰', '孤虎', '孤豹', '孤蛇', '孤鲨', '孤鲸', '孤鹿', '孤雁', '孤鸿'] },
{ initial: 'H', nameList: ['海鸥', '海龟', '海豚', '海星', '海马', '海葵', '海参', '海胆', '海螺', '海贝'] },
{ initial: 'I', nameList: ['火焰', '火球', '火箭', '火山', '火车', '火柴', '火把', '火鸟'] },
{ initial: 'J', nameList: ['金鱼', '金狮', '金刚', '金鹿', '金蛇', '金鹰', '金豹', '金虎', '金狐', '金猫'] },
{ initial: 'K', nameList: ['孔雀', '恐龙', '开心', '开怀', '开朗', '开拓', '开口', '开花', '开眼', '开天'] },
{ initial: 'L', nameList: ['老虎', '老鹰', '老鼠', '老狼', '老狗', '老猫', '老熊', '老鹿', '老龟', '老蛇'] },
{ initial: 'M', nameList: ['玫瑰', '牡丹', '梅花', '茉莉', '木兰', '棉花', '蜜蜂', '蚂蚁', '马蜂', '蟒蛇'] },
{ initial: 'N', nameList: ['南山', '南极', '南海', '南京', '南阳', '南风', '南瓜', '南竹', '南花', '南鸟'] },
{
initial: 'O',
nameList: ['熊猫', '欧鹭', '欧洲', '欧阳', '欧文', '欧若拉', '欧米茄', '欧罗巴', '欧菲莉亚', '欧瑞斯']
},
{ initial: 'P', nameList: ['苹果', '葡萄', '琵琶', '枇杷', '菩提', '瓢虫', '瓢泼', '飘零', '飘渺', '飘飘然'] },
{ initial: 'Q', nameList: ['七喜', '强风', '奇迹', '乾坤', '奇才', '晴天', '青竹', '秋水', '轻舞', '清泉'] },
{ initial: 'R', nameList: ['瑞雪', '瑞兽', '瑞光', '瑞云', '瑞彩', '瑞气', '瑞香', '瑞草', '瑞莲', '瑞竹'] },
{ initial: 'S', nameList: ['三羊', '三狗', '三猫', '三鱼', '三角', '三鹿', '三鹰', '三蛇', '三狐', '三豹'] },
{ initial: 'T', nameList: ['太阳', '天空', '田园', '太极', '太湖', '天鹅', '太空', '天使', '坦克', '甜橙'] },
{ initial: 'U', nameList: ['乌鸦', '乌鹊', '乌鱼', '乌龟', '乌云', '乌梅', '乌木', '乌金', '乌黑', '乌青'] },
{ initial: 'V', nameList: ['五虎', '五狼', '五鹰', '五豹', '五熊', '五蛇', '五鲨', '五鲸', '五鹿', '五马'] },
{ initial: 'W', nameList: ['悟空', '微笑', '温暖', '无畏', '温柔', '舞蹈', '问心', '悟道', '未来', '文学'] },
{ initial: 'X', nameList: ['西风', '西洋', '西子', '西施', '西岳', '西湖', '西柚', '西竹', '西花', '西鸟'] },
{ initial: 'Y', nameList: ['夜猫', '夜鹰', '夜莺', '夜空', '夜色', '夜月', '夜影', '夜翼', '夜狐', '夜狼'] },
{ initial: 'Z', nameList: ['珍珠', '紫薇', '紫霞', '紫竹', '紫云', '紫燕', '紫鸢', '紫藤', '紫荆', '紫罗兰'] },
]
build() {
Column() {
Stack({ alignContent: Alignment.End }) {
Text('通讯录')
.width('100%')
.textAlign(TextAlign.Center)
.fontSize(20)
Image($r('app.media.ic_public_add'))
.width(20)
}
.width('100%')
.padding(15)
.backgroundColor('#fff1f3f5')
List() {
// 顶部区域
ListItem() {
Row() {
Image($r('app.media.ic_public_search'))
.width(20)
.fillColor(Color.Gray)
Text('搜索')
.fontColor(Color.Gray)
}
.backgroundColor(Color.White)
.width('100%')
.height(40)
.borderRadius(5)
.justifyContent(FlexAlign.Center)
}
.padding(10)
.width('100%')
.backgroundColor('#fff1f3f5')
// 分组的联系人信息
ListItemGroup({ header: this.itemHead('A'), space: 10 }) {
// 循环渲染分组A的ListItem
ListItem() {
Row({ space: 10 }) {
Image($r('app.media.ic_public_lianxiren'))
.width(40)
.fillColor('#0094ff')
Text('昵称')
}
}
}
.divider({
startMargin: 60,
strokeWidth: 1,
color: '#ccc'
})
}
}
}
@Builder
itemHead(text: string) {
// 列表分组的头部组件,对应联系人分组A、B等位置的组件
Text(text)
.fontSize(20)
.backgroundColor('#fff1f3f5')
.width('100%')
.padding(5)
}
}
核心步骤:
- 基于数据渲染列表
- 看清楚数据,分两次渲染
- 外层循环,渲染ListItemGroup
- 内层循环,渲染 ListItem
参考代码;
interface ContactContent {
initial: string
nameList: string[]
}
@Entry
@Component
struct Page07_ListDemo_Contact {
contacts: ContactContent[] = [
{ initial: 'A', nameList: ['阿猫', '阿狗', '阿虎', '阿龙', '阿鹰', '阿狼', '阿豹', '阿狮', '阿象', '阿鲸'] },
{ initial: 'B', nameList: ['白兔', '白鸽', '白鹤', '白鹭', '白狐', '白狼', '白虎', '白鹿', '白蛇', '白马'] },
{ initial: 'C', nameList: ['春花', '春风', '春雨', '春草', '春柳', '春燕', '春莺', '春蝶', '春蓝', '春绿'] },
{ initial: 'D', nameList: ['冬雪', '冬梅', '冬松', '冬竹', '冬云', '冬霜', '冬月', '冬夜', '冬青', '冬红'] },
{ initial: 'E', nameList: ['饿狼', '饿虎', '饿鹰', '饿豹', '饿熊', '饿蛇', '饿鱼', '饿虾', '饿蟹', '饿蚌'] },
{ initial: 'F', nameList: ['飞鸟', '飞鱼', '飞虫', '飞蜂', '飞蝶', '飞蛾', '飞蝉', '飞蝗', '飞鼠', '飞猫'] },
{ initial: 'G', nameList: ['孤狼', '孤鹰', '孤虎', '孤豹', '孤蛇', '孤鲨', '孤鲸', '孤鹿', '孤雁', '孤鸿'] },
{ initial: 'H', nameList: ['海鸥', '海龟', '海豚', '海星', '海马', '海葵', '海参', '海胆', '海螺', '海贝'] },
{ initial: 'I', nameList: ['火焰', '火球', '火箭', '火山', '火车', '火柴', '火把', '火鸟'] },
{ initial: 'J', nameList: ['金鱼', '金狮', '金刚', '金鹿', '金蛇', '金鹰', '金豹', '金虎', '金狐', '金猫'] },
{ initial: 'K', nameList: ['孔雀', '恐龙', '开心', '开怀', '开朗', '开拓', '开口', '开花', '开眼', '开天'] },
{ initial: 'L', nameList: ['老虎', '老鹰', '老鼠', '老狼', '老狗', '老猫', '老熊', '老鹿', '老龟', '老蛇'] },
{ initial: 'M', nameList: ['玫瑰', '牡丹', '梅花', '茉莉', '木兰', '棉花', '蜜蜂', '蚂蚁', '马蜂', '蟒蛇'] },
{ initial: 'N', nameList: ['南山', '南极', '南海', '南京', '南阳', '南风', '南瓜', '南竹', '南花', '南鸟'] },
{
initial: 'O',
nameList: ['熊猫', '欧鹭', '欧洲', '欧阳', '欧文', '欧若拉', '欧米茄', '欧罗巴', '欧菲莉亚', '欧瑞斯']
},
{ initial: 'P', nameList: ['苹果', '葡萄', '琵琶', '枇杷', '菩提', '瓢虫', '瓢泼', '飘零', '飘渺', '飘飘然'] },
{ initial: 'Q', nameList: ['七喜', '强风', '奇迹', '乾坤', '奇才', '晴天', '青竹', '秋水', '轻舞', '清泉'] },
{ initial: 'R', nameList: ['瑞雪', '瑞兽', '瑞光', '瑞云', '瑞彩', '瑞气', '瑞香', '瑞草', '瑞莲', '瑞竹'] },
{ initial: 'S', nameList: ['三羊', '三狗', '三猫', '三鱼', '三角', '三鹿', '三鹰', '三蛇', '三狐', '三豹'] },
{ initial: 'T', nameList: ['太阳', '天空', '田园', '太极', '太湖', '天鹅', '太空', '天使', '坦克', '甜橙'] },
{ initial: 'U', nameList: ['乌鸦', '乌鹊', '乌鱼', '乌龟', '乌云', '乌梅', '乌木', '乌金', '乌黑', '乌青'] },
{ initial: 'V', nameList: ['五虎', '五狼', '五鹰', '五豹', '五熊', '五蛇', '五鲨', '五鲸', '五鹿', '五马'] },
{ initial: 'W', nameList: ['悟空', '微笑', '温暖', '无畏', '温柔', '舞蹈', '问心', '悟道', '未来', '文学'] },
{ initial: 'X', nameList: ['西风', '西洋', '西子', '西施', '西岳', '西湖', '西柚', '西竹', '西花', '西鸟'] },
{ initial: 'Y', nameList: ['夜猫', '夜鹰', '夜莺', '夜空', '夜色', '夜月', '夜影', '夜翼', '夜狐', '夜狼'] },
{ initial: 'Z', nameList: ['珍珠', '紫薇', '紫霞', '紫竹', '紫云', '紫燕', '紫鸢', '紫藤', '紫荆', '紫罗兰'] },
]
build() {
Column() {
Stack({ alignContent: Alignment.End }) {
Text('通讯录')
.width('100%')
.textAlign(TextAlign.Center)
.fontSize(20)
Image($r('app.media.ic_public_add'))
.width(20)
}
.width('100%')
.padding(15)
.backgroundColor('#fff1f3f5')
List() {
// 顶部区域
ListItem() {
Row() {
Image($r('app.media.ic_public_search'))
.width(20)
.fillColor(Color.Gray)
Text('搜索')
.fontColor(Color.Gray)
}
.backgroundColor(Color.White)
.width('100%')
.height(40)
.borderRadius(5)
.justifyContent(FlexAlign.Center)
}
.padding(10)
.width('100%')
.backgroundColor('#fff1f3f5')
ForEach(this.contacts, (item: ContactContent, index: number) => {
// 分组的联系人信息
ListItemGroup({ header: this.itemHead(item.initial), space: 10 }) {
ForEach(item.nameList, (it: string, i: number) => {
// 循环渲染分组A的ListItem
ListItem() {
Row({ space: 10 }) {
Image($r('app.media.ic_public_lianxiren'))
.width(40)
.fillColor('#0094ff')
Text(it)
}
}
})
}
.divider({
startMargin: 60,
strokeWidth: 1,
color: '#ccc'
})
})
}
}
}
@Builder
itemHead(text: string) {
// 列表分组的头部组件,对应联系人分组A、B等位置的组件
Text(text)
.fontSize(20)
.backgroundColor('#fff1f3f5')
.width('100%')
.padding(5)
}
}
2.4.2. 需求2-随机颜色
目前的代码 每个联系人的头像颜色都是一样的,咱们来实现为每个头像设置随机颜色
需求:
- 实现随机颜色函数
getRamdomColor()
,调用返回随机颜色rgba(随机数,随机数,随机数,0.5)
- 使用随机颜色函数,调整联系人头像颜色
- HarmonyOs DevEco Studio小技巧3--获取随机颜色_harmony 随机一种颜色-CSDN博客
getRandomColor(){
// 逻辑略
return `rgba(随机数,随机数,随机数,0.5)`
}
this.getRandomColor()
// 返回随机颜色字符串,比如 rgba('随机数,随机数,随机数,0.5')
// 其中随机数的值为 0-255
参考答案:
interface ContactContent {
initial: string
nameList: string[]
}
@Entry
@Component
struct Page07_ListDemo_Contact {
contacts: ContactContent[] = [
{ initial: 'A', nameList: ['阿猫', '阿狗', '阿虎', '阿龙', '阿鹰', '阿狼', '阿豹', '阿狮', '阿象', '阿鲸'] },
{ initial: 'B', nameList: ['白兔', '白鸽', '白鹤', '白鹭', '白狐', '白狼', '白虎', '白鹿', '白蛇', '白马'] },
{ initial: 'C', nameList: ['春花', '春风', '春雨', '春草', '春柳', '春燕', '春莺', '春蝶', '春蓝', '春绿'] },
{ initial: 'D', nameList: ['冬雪', '冬梅', '冬松', '冬竹', '冬云', '冬霜', '冬月', '冬夜', '冬青', '冬红'] },
{ initial: 'E', nameList: ['饿狼', '饿虎', '饿鹰', '饿豹', '饿熊', '饿蛇', '饿鱼', '饿虾', '饿蟹', '饿蚌'] },
{ initial: 'F', nameList: ['飞鸟', '飞鱼', '飞虫', '飞蜂', '飞蝶', '飞蛾', '飞蝉', '飞蝗', '飞鼠', '飞猫'] },
{ initial: 'G', nameList: ['孤狼', '孤鹰', '孤虎', '孤豹', '孤蛇', '孤鲨', '孤鲸', '孤鹿', '孤雁', '孤鸿'] },
{ initial: 'H', nameList: ['海鸥', '海龟', '海豚', '海星', '海马', '海葵', '海参', '海胆', '海螺', '海贝'] },
{ initial: 'I', nameList: ['火焰', '火球', '火箭', '火山', '火车', '火柴', '火把', '火鸟'] },
{ initial: 'J', nameList: ['金鱼', '金狮', '金刚', '金鹿', '金蛇', '金鹰', '金豹', '金虎', '金狐', '金猫'] },
{ initial: 'K', nameList: ['孔雀', '恐龙', '开心', '开怀', '开朗', '开拓', '开口', '开花', '开眼', '开天'] },
{ initial: 'L', nameList: ['老虎', '老鹰', '老鼠', '老狼', '老狗', '老猫', '老熊', '老鹿', '老龟', '老蛇'] },
{ initial: 'M', nameList: ['玫瑰', '牡丹', '梅花', '茉莉', '木兰', '棉花', '蜜蜂', '蚂蚁', '马蜂', '蟒蛇'] },
{ initial: 'N', nameList: ['南山', '南极', '南海', '南京', '南阳', '南风', '南瓜', '南竹', '南花', '南鸟'] },
{
initial: 'O',
nameList: ['熊猫', '欧鹭', '欧洲', '欧阳', '欧文', '欧若拉', '欧米茄', '欧罗巴', '欧菲莉亚', '欧瑞斯']
},
{ initial: 'P', nameList: ['苹果', '葡萄', '琵琶', '枇杷', '菩提', '瓢虫', '瓢泼', '飘零', '飘渺', '飘飘然'] },
{ initial: 'Q', nameList: ['七喜', '强风', '奇迹', '乾坤', '奇才', '晴天', '青竹', '秋水', '轻舞', '清泉'] },
{ initial: 'R', nameList: ['瑞雪', '瑞兽', '瑞光', '瑞云', '瑞彩', '瑞气', '瑞香', '瑞草', '瑞莲', '瑞竹'] },
{ initial: 'S', nameList: ['三羊', '三狗', '三猫', '三鱼', '三角', '三鹿', '三鹰', '三蛇', '三狐', '三豹'] },
{ initial: 'T', nameList: ['太阳', '天空', '田园', '太极', '太湖', '天鹅', '太空', '天使', '坦克', '甜橙'] },
{ initial: 'U', nameList: ['乌鸦', '乌鹊', '乌鱼', '乌龟', '乌云', '乌梅', '乌木', '乌金', '乌黑', '乌青'] },
{ initial: 'V', nameList: ['五虎', '五狼', '五鹰', '五豹', '五熊', '五蛇', '五鲨', '五鲸', '五鹿', '五马'] },
{ initial: 'W', nameList: ['悟空', '微笑', '温暖', '无畏', '温柔', '舞蹈', '问心', '悟道', '未来', '文学'] },
{ initial: 'X', nameList: ['西风', '西洋', '西子', '西施', '西岳', '西湖', '西柚', '西竹', '西花', '西鸟'] },
{ initial: 'Y', nameList: ['夜猫', '夜鹰', '夜莺', '夜空', '夜色', '夜月', '夜影', '夜翼', '夜狐', '夜狼'] },
{ initial: 'Z', nameList: ['珍珠', '紫薇', '紫霞', '紫竹', '紫云', '紫燕', '紫鸢', '紫藤', '紫荆', '紫罗兰'] },
]
// 随机颜色函数
getRandomColor(): ResourceColor {
// 生成 0-255 的随机数
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
// 拼接成随机的颜色,半透明并返回
return `rgba(${r}, ${g}, ${b}, 0.5)`;
}
build() {
Column() {
Stack({ alignContent: Alignment.End }) {
Text('通讯录')
.width('100%')
.textAlign(TextAlign.Center)
.fontSize(20)
Image($r('app.media.ic_public_add'))
.width(20)
}
.width('100%')
.padding(15)
.backgroundColor('#fff1f3f5')
List() {
// 顶部区域
ListItem() {
Row() {
Image($r('app.media.ic_public_search'))
.width(20)
.fillColor(Color.Gray)
Text('搜索')
.fontColor(Color.Gray)
}
.backgroundColor(Color.White)
.width('100%')
.height(40)
.borderRadius(5)
.justifyContent(FlexAlign.Center)
}
.padding(10)
.width('100%')
.backgroundColor('#fff1f3f5')
ForEach(this.contacts, (item: ContactContent, index: number) => {
// 分组的联系人信息
ListItemGroup({ header: this.itemHead(item.initial), space: 10 }) {
ForEach(item.nameList, (it: string, i: number) => {
// 循环渲染分组A的ListItem
ListItem() {
Row({ space: 10 }) {
Image($r('app.media.ic_public_lianxiren'))
.width(40)
.fillColor(this.getRandomColor())
Text(it)
}
}
})
}
.divider({
startMargin: 60,
strokeWidth: 1,
color: '#ccc'
})
})
}
}
}
@Builder
itemHead(text: string) {
// 列表分组的头部组件,对应联系人分组A、B等位置的组件
Text(text)
.fontSize(20)
.backgroundColor('#fff1f3f5')
.width('100%')
.padding(5)
}
}
2.5. 粘性标题
通过List组件的sticky属性,即可实现粘性标题
sticky | 配合ListItemGroup组件使用,设置ListItemGroup中header和footer是否要吸顶或吸底。 默认值:StickyStyle.None 该接口支持在ArkTS卡片中使用。 说明: sticky属性可以设置为 StickyStyle.Header | StickyStyle.Footer 以同时支持header吸顶和footer吸底。 |
List(){
}
// .sticky(StickyStyle.None) // 不吸附 默认值
// .sticky(StickyStyle.Header) // 头部吸附
// .sticky(StickyStyle. Footer) // 底部吸附,如果有的话
试一试:
- 使用 sticky属性将上一节的案例设置 粘性标题
2.6. 控制滚动
如果列表很长,需要快速滚动到列表底部或返回列表顶部,就可以使用控制器来控制滚动
核心步骤:
- 创建控制器(Scroller)对象,不需要添加@State
- 设置给 List 组件
- 调用控制器对象的方法,实现滚动
// 1. 创建控制器(ListScroller)对象
scroller: Scroller = new Scroller()
// 2. 设置给 List 组件
List({ space: 20, scroller: this.scroller }) {
// ...
}
Button() {
// ...
}
.onClick(() => {
// 3. 调用控制器对象的方法,实现滚动
this.scroller.scrollToIndex(0)
})
scrollToIndex方法参数:
参数名 | 参数类型 | 必填 | 参数描述 |
index | number | 是 | 要滑动到的目标元素所在的ListItemGroup在当前容器中的索引值。 |
smooth | boolean | 否 | 设置滑动到列表项在列表中的索引值时是否有动效,true表示有动效,false表示没有动效。 默认值:false。 |
align | ScrollAlign | 否 | 指定滑动到的元素与当前容器的对齐方式。 默认值:ScrollAlign.START。 |
试一试:
- 调整 案例-通讯录的代码,实现点击标题返回顶部
核心步骤:
- 创建控制器(ListScroller)对象,不需要添加@State
- 设置给 List 组件
- 调用控制器对象的方法,实现滚动
参考代码:
interface ContactContent {
initial: string
nameList: string[]
}
@Entry
@Component
struct Page07_ListDemo_Contact {
contacts: ContactContent[] = [
{ initial: 'A', nameList: ['阿猫', '阿狗', '阿虎', '阿龙', '阿鹰', '阿狼', '阿豹', '阿狮', '阿象', '阿鲸'] },
{ initial: 'B', nameList: ['白兔', '白鸽', '白鹤', '白鹭', '白狐', '白狼', '白虎', '白鹿', '白蛇', '白马'] },
{ initial: 'C', nameList: ['春花', '春风', '春雨', '春草', '春柳', '春燕', '春莺', '春蝶', '春蓝', '春绿'] },
{ initial: 'D', nameList: ['冬雪', '冬梅', '冬松', '冬竹', '冬云', '冬霜', '冬月', '冬夜', '冬青', '冬红'] },
{ initial: 'E', nameList: ['饿狼', '饿虎', '饿鹰', '饿豹', '饿熊', '饿蛇', '饿鱼', '饿虾', '饿蟹', '饿蚌'] },
{ initial: 'F', nameList: ['飞鸟', '飞鱼', '飞虫', '飞蜂', '飞蝶', '飞蛾', '飞蝉', '飞蝗', '飞鼠', '飞猫'] },
{ initial: 'G', nameList: ['孤狼', '孤鹰', '孤虎', '孤豹', '孤蛇', '孤鲨', '孤鲸', '孤鹿', '孤雁', '孤鸿'] },
{ initial: 'H', nameList: ['海鸥', '海龟', '海豚', '海星', '海马', '海葵', '海参', '海胆', '海螺', '海贝'] },
{ initial: 'I', nameList: ['火焰', '火球', '火箭', '火山', '火车', '火柴', '火把', '火鸟'] },
{ initial: 'J', nameList: ['金鱼', '金狮', '金刚', '金鹿', '金蛇', '金鹰', '金豹', '金虎', '金狐', '金猫'] },
{ initial: 'K', nameList: ['孔雀', '恐龙', '开心', '开怀', '开朗', '开拓', '开口', '开花', '开眼', '开天'] },
{ initial: 'L', nameList: ['老虎', '老鹰', '老鼠', '老狼', '老狗', '老猫', '老熊', '老鹿', '老龟', '老蛇'] },
{ initial: 'M', nameList: ['玫瑰', '牡丹', '梅花', '茉莉', '木兰', '棉花', '蜜蜂', '蚂蚁', '马蜂', '蟒蛇'] },
{ initial: 'N', nameList: ['南山', '南极', '南海', '南京', '南阳', '南风', '南瓜', '南竹', '南花', '南鸟'] },
{
initial: 'O',
nameList: ['熊猫', '欧鹭', '欧洲', '欧阳', '欧文', '欧若拉', '欧米茄', '欧罗巴', '欧菲莉亚', '欧瑞斯']
},
{ initial: 'P', nameList: ['苹果', '葡萄', '琵琶', '枇杷', '菩提', '瓢虫', '瓢泼', '飘零', '飘渺', '飘飘然'] },
{ initial: 'Q', nameList: ['七喜', '强风', '奇迹', '乾坤', '奇才', '晴天', '青竹', '秋水', '轻舞', '清泉'] },
{ initial: 'R', nameList: ['瑞雪', '瑞兽', '瑞光', '瑞云', '瑞彩', '瑞气', '瑞香', '瑞草', '瑞莲', '瑞竹'] },
{ initial: 'S', nameList: ['三羊', '三狗', '三猫', '三鱼', '三角', '三鹿', '三鹰', '三蛇', '三狐', '三豹'] },
{ initial: 'T', nameList: ['太阳', '天空', '田园', '太极', '太湖', '天鹅', '太空', '天使', '坦克', '甜橙'] },
{ initial: 'U', nameList: ['乌鸦', '乌鹊', '乌鱼', '乌龟', '乌云', '乌梅', '乌木', '乌金', '乌黑', '乌青'] },
{ initial: 'V', nameList: ['五虎', '五狼', '五鹰', '五豹', '五熊', '五蛇', '五鲨', '五鲸', '五鹿', '五马'] },
{ initial: 'W', nameList: ['悟空', '微笑', '温暖', '无畏', '温柔', '舞蹈', '问心', '悟道', '未来', '文学'] },
{ initial: 'X', nameList: ['西风', '西洋', '西子', '西施', '西岳', '西湖', '西柚', '西竹', '西花', '西鸟'] },
{ initial: 'Y', nameList: ['夜猫', '夜鹰', '夜莺', '夜空', '夜色', '夜月', '夜影', '夜翼', '夜狐', '夜狼'] },
{ initial: 'Z', nameList: ['珍珠', '紫薇', '紫霞', '紫竹', '紫云', '紫燕', '紫鸢', '紫藤', '紫荆', '紫罗兰'] },
]
// 随机颜色函数
getRandomColor(): ResourceColor {
// 生成 0-255 的随机数
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
// 拼接成随机的颜色,半透明并返回
return `rgba(${r}, ${g}, ${b}, 0.5)`;
}
// 1.控制器对象
scroller: Scroller = new Scroller()
build() {
Column() {
Stack({ alignContent: Alignment.End }) {
Text('通讯录')
.width('100%')
.textAlign(TextAlign.Center)
.fontSize(20)
.onClick(() => {
// 3.调用方法控制滚动
this.scroller.scrollToIndex(0, true)
})
Image($r('app.media.ic_public_add'))
.width(20)
}
.width('100%')
.padding(15)
.backgroundColor('#fff1f3f5')
// 2. 设置给 List 组件
List({ scroller: this.scroller }) {
// 顶部区域
ListItem() {
Row() {
Image($r('app.media.ic_public_search'))
.width(20)
.fillColor(Color.Gray)
Text('搜索')
.fontColor(Color.Gray)
}
.backgroundColor(Color.White)
.width('100%')
.height(40)
.borderRadius(5)
.justifyContent(FlexAlign.Center)
}
.padding(10)
.width('100%')
.backgroundColor('#fff1f3f5')
ForEach(this.contacts, (item: ContactContent, index: number) => {
// 分组的联系人信息
ListItemGroup({ header: this.itemHead(item.initial), space: 10 }) {
ForEach(item.nameList, (it: string, i: number) => {
// 循环渲染分组A的ListItem
ListItem() {
Row({ space: 10 }) {
Image($r('app.media.ic_public_lianxiren'))
.width(40)
.fillColor(this.getRandomColor())
Text(it)
}
}
})
}
.divider({
startMargin: 60,
strokeWidth: 1,
color: '#ccc'
})
})
}
}
}
@Builder
itemHead(text: string) {
// 列表分组的头部组件,对应联系人分组A、B等位置的组件
Text(text)
.fontSize(20)
.backgroundColor('#fff1f3f5')
.width('100%')
.padding(5)
}
}
2.7. List事件
List 组件提供了很多事件供咱们使用,目前掌握一个即可onScrollIndex,其他的事件后续结合业务逻辑再进行补充
名称 | 功能描述 |
onScrollIndex(event: (start: number, end: number, center10+: number) => void) | 有子组件划入或划出List显示区域时触发。从API version 10开始,List显示区域中间位置子组件变化时也会触发。 计算索引值时,ListItemGroup作为一个整体占一个索引值,不计算ListItemGroup内部ListItem的索引值。 - start: List显示区域内第一个子组件的索引值。 - end: List显示区域内最后一个子组件的索引值。 - center: List显示区域内中间位置子组件的索引值。 |
List(){
// ...
}
.onScrollIndex((index: number) => {
console.log('index:', index)
})
3. AlphabetIndexer组件
通过 AlphabetIndexer 组件可以与容器组件结合,实现导航联动,以及快速定位的效果
3.1. 核心用法:
AlphabetIndexer不是容器组件,属于功能类的组件,使用时,需要设置如下 2 个参数
参数名 | 参数类型 | 必填 | 参数描述 |
arrayValue | Array<string> | 是 | 字母索引字符串数组,不可设置为空。 |
selected | number | 是 | 初始选中项索引值,若超出索引值范围,则取默认值0。 从API version 10开始,该参数支持$$双向绑定变量。 |
参考代码
@Entry
@Component
struct Page08_AlphabetIndexer {
alphabets: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
@State selectedIndex: number = 0
build() {
Stack({ alignContent: Alignment.End }) {
Text('选中的索引为:' + this.selectedIndex)
.width('100%')
.textAlign(TextAlign.Center)
.onClick(() => {
this.selectedIndex = 10
})
// 字母表索引组件
// arrayValue 索引项
// selected 选中索引 ,支持双向绑定
AlphabetIndexer({ arrayValue: this.alphabets, selected: $$this.selectedIndex })
}
.width('100%')
.height('100%')
}
}
注意:
- 通过双向绑定变量。可以实现修改变量值,同步更新选中的索引
3.2. 外观设置
3.2.1. 设置文字外观
选中AlphabetIndexer,如果默认的颜色效果无法满足要求,可以通过如下属性来进行设置
名称 | 参数类型 | 描述 |
color | 设置文字颜色。 | |
itemSize | number | 设置每个字母的区域大小 |
font | Font | 设置每个字母的字体样式 |
selectedFont | Font | 设置选中字母的字体样式 |
selectedColor | 设置选中项文字颜色。 | |
selectedBackgroundColor | 设置选中项背景颜色。 |
@Entry
@Component
struct Page08_AlphabetIndexer {
alphabets: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
@State selecteIndex: number = 0
build() {
Stack({ alignContent: Alignment.End }) {
Text('选中的索引为:' + this.selecteIndex)
.width('100%')
.textAlign(TextAlign.Center)
.onClick(() => {
this.selecteIndex = 10
})
// 字母表索引组件
// arrayValue 索引项
// selected 选中索引 ,支持双向绑定
AlphabetIndexer({ arrayValue: this.alphabets, selected: $$this.selecteIndex })
.color(Color.Orange)// 文字颜色
.selectedColor(Color.Green)// 选中文字颜色
.selectedBackgroundColor(Color.Black) // 选中背景颜色
}
.width('100%')
.height('100%')
}
}
3.2.2. 弹窗提示
如果默认的文字高亮效果无法满足要求,还可以通过弹框来显示,对应属性如下
@Entry
@Component
struct Page08_AlphabetIndexer {
alphabets: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
@State selecteIndex: number = 0
build() {
Stack({ alignContent: Alignment.End }) {
Text('选中的索引为:' + this.selecteIndex)
.width('100%')
.textAlign(TextAlign.Center)
.onClick(() => {
this.selecteIndex = 10
})
// 字母表索引组件
// arrayValue 索引项
// selected 选中索引 ,支持双向绑定
AlphabetIndexer({ arrayValue: this.alphabets, selected: $$this.selecteIndex })
.color(Color.Orange)// 文字颜色
.selectedColor(Color.Green)// 选中文字颜色
.selectedBackgroundColor(Color.Black)// 选中背景颜色
.usingPopup(true)// 显示弹窗
.popupColor(Color.Orange)// 弹窗文字颜色
.popupBackground(Color.Pink) // 弹窗背景色
}
.width('100%')
.height('100%')
}
}
3.3. 事件
支持的常用事件是onSelect,会在选中不同项时触发
名称 | 功能描述 |
onSelect(callback: (index: number) => void)8+ | 索引条选中回调,返回值为当前选中索引。 |
.... 其他事件后续用到再补充,目前掌握这一个即可 |
@Entry
@Component
struct Page08_AlphabetIndexer {
alphabets: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
@State selectedIndex: number = 0
build() {
Stack({ alignContent: Alignment.End }) {
Text('选中的索引为:' + this.selectedIndex)
.width('100%')
.textAlign(TextAlign.Center)
.onClick(() => {
this.selectedIndex = 10
})
// 字母表索引组件
// arrayValue 索引项
// selected 选中索引 ,支持双向绑定
AlphabetIndexer({ arrayValue: this.alphabets, selected: $$this.selectedIndex })
.color(Color.Orange)// 文字颜色
.selectedColor(Color.Green)// 选中文字颜色
.selectedBackgroundColor(Color.Black)// 选中背景颜色
.usingPopup(true)// 显示弹窗
.popupColor(Color.Orange)// 弹窗文字颜色
.popupBackground(Color.Pink)// 弹窗背景色
.onSelect((index: number) => {
// this.selectedIndex = index
console.log('选中的索引是:', index)
})
}
.width('100%')
.height('100%')
}
}
3.4. 案例-用户列表联动
和上一节 List 的用户列表进行联动,实现选中切换的联动效果
需求:
- 整合 AlphabetIndexer 到联系人案例中
- 滚动 List,同步选中对应的AlphabetIndexer
- 选中AlphabetIndexer的区域,同步滚动List
核心步骤:
- 整合 AlphabetIndexer 到联系人案例中
- 整合数组,定义 状态变量 记录索引
- 整合AlphabetIndexer组件,设置 导航数组 和 索引
- 使用 Stack 作为容器,调整位置
- 滚动 List,同步选中对应的AlphabetIndexer
- List组件注册 onScrollIndex 事件,获取索引值
- 将上一步获取到的索引值设置给 记录索引的状态变量
- 选中AlphabetIndexer的区域,同步滚动List
- AlphabetIndexer组件注册onSelect事件,获取选中的索引值
- 调用 List 组件的控制器的 scrollToIndex方法,结合获取到的索引进行滚动
3.4.1. 基础模版:
interface ContactData {
initial: string
nameList: string[]
}
@Entry
@Component
struct Page09_ContactAndAlpha {
// contacts,alphabets 不需要修改,只是用来渲染,可以不添加@State
contacts: ContactData[] = [
{ initial: 'A', nameList: ['阿猫', '阿狗', '阿虎', '阿龙', '阿鹰', '阿狼', '阿豹', '阿狮', '阿象', '阿鲸'] },
{ initial: 'B', nameList: ['白兔', '白鸽', '白鹤', '白鹭', '白狐', '白狼', '白虎', '白鹿', '白蛇', '白马'] },
{ initial: 'C', nameList: ['春花', '春风', '春雨', '春草', '春柳', '春燕', '春莺', '春蝶', '春蓝', '春绿'] },
{ initial: 'D', nameList: ['冬雪', '冬梅', '冬松', '冬竹', '冬云', '冬霜', '冬月', '冬夜', '冬青', '冬红'] },
{ initial: 'E', nameList: ['饿狼', '饿虎', '饿鹰', '饿豹', '饿熊', '饿蛇', '饿鱼', '饿虾', '饿蟹', '饿蚌'] },
{ initial: 'F', nameList: ['飞鸟', '飞鱼', '飞虫', '飞蜂', '飞蝶', '飞蛾', '飞蝉', '飞蝗', '飞鼠', '飞猫'] },
{ initial: 'G', nameList: ['孤狼', '孤鹰', '孤虎', '孤豹', '孤蛇', '孤鲨', '孤鲸', '孤鹿', '孤雁', '孤鸿'] },
{ initial: 'H', nameList: ['海鸥', '海龟', '海豚', '海星', '海马', '海葵', '海参', '海胆', '海螺', '海贝'] },
{ initial: 'I', nameList: ['火焰', '火球', '火箭', '火山', '火车', '火柴', '火把', '火鸟'] },
{ initial: 'J', nameList: ['金鱼', '金狮', '金刚', '金鹿', '金蛇', '金鹰', '金豹', '金虎', '金狐', '金猫'] },
{ initial: 'K', nameList: ['孔雀', '恐龙', '开心', '开怀', '开朗', '开拓', '开口', '开花', '开眼', '开天'] },
{ initial: 'L', nameList: ['老虎', '老鹰', '老鼠', '老狼', '老狗', '老猫', '老熊', '老鹿', '老龟', '老蛇'] },
{ initial: 'M', nameList: ['玫瑰', '牡丹', '梅花', '茉莉', '木兰', '棉花', '蜜蜂', '蚂蚁', '马蜂', '蟒蛇'] },
{ initial: 'N', nameList: ['南山', '南极', '南海', '南京', '南阳', '南风', '南瓜', '南竹', '南花', '南鸟'] },
{
initial: 'O',
nameList: ['熊猫', '欧鹭', '欧洲', '欧阳', '欧文', '欧若拉', '欧米茄', '欧罗巴', '欧菲莉亚', '欧瑞斯']
},
{ initial: 'P', nameList: ['苹果', '葡萄', '琵琶', '枇杷', '菩提', '瓢虫', '瓢泼', '飘零', '飘渺', '飘飘然'] },
{ initial: 'Q', nameList: ['七喜', '强风', '奇迹', '乾坤', '奇才', '晴天', '青竹', '秋水', '轻舞', '清泉'] },
{ initial: 'R', nameList: ['瑞雪', '瑞兽', '瑞光', '瑞云', '瑞彩', '瑞气', '瑞香', '瑞草', '瑞莲', '瑞竹'] },
{ initial: 'S', nameList: ['三羊', '三狗', '三猫', '三鱼', '三角', '三鹿', '三鹰', '三蛇', '三狐', '三豹'] },
{ initial: 'T', nameList: ['太阳', '天空', '田园', '太极', '太湖', '天鹅', '太空', '天使', '坦克', '甜橙'] },
{ initial: 'U', nameList: ['乌鸦', '乌鹊', '乌鱼', '乌龟', '乌云', '乌梅', '乌木', '乌金', '乌黑', '乌青'] },
{ initial: 'V', nameList: ['五虎', '五狼', '五鹰', '五豹', '五熊', '五蛇', '五鲨', '五鲸', '五鹿', '五马'] },
{ initial: 'W', nameList: ['悟空', '微笑', '温暖', '无畏', '温柔', '舞蹈', '问心', '悟道', '未来', '文学'] },
{ initial: 'X', nameList: ['西风', '西洋', '西子', '西施', '西岳', '西湖', '西柚', '西竹', '西花', '西鸟'] },
{ initial: 'Y', nameList: ['夜猫', '夜鹰', '夜莺', '夜空', '夜色', '夜月', '夜影', '夜翼', '夜狐', '夜狼'] },
{ initial: 'Z', nameList: ['珍珠', '紫薇', '紫霞', '紫竹', '紫云', '紫燕', '紫鸢', '紫藤', '紫荆', '紫罗兰'] },
]
alphabets: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
// 随机颜色函数
getRandomColor(): ResourceColor {
// 生成 0-255 的随机数
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
// 拼接成随机的颜色,半透明并返回
return `rgba(${r}, ${g}, ${b}, 0.5)`;
}
// 控制器对象
scroller: Scroller = new Scroller()
build() {
Column() {
Stack({ alignContent: Alignment.End }) {
Text('通讯录')
.width('100%')
.textAlign(TextAlign.Center)
.fontSize(20)
.onClick(() => {
this.scroller.scrollToIndex(0, true)
})
Image($r('app.media.ic_public_add'))
.width(20)
}
.width('100%')
.padding(15)
.backgroundColor('#fff1f3f5')
Stack({ alignContent: Alignment.End }) {
List({ scroller: this.scroller }) {
// 顶部区域
ListItem() {
Row() {
Image($r('app.media.ic_public_search'))
.width(20)
.fillColor(Color.Gray)
Text('搜索')
.fontColor(Color.Gray)
}
.backgroundColor(Color.White)
.width('100%')
.height(40)
.borderRadius(5)
.justifyContent(FlexAlign.Center)
}
.padding(10)
.width('100%')
.backgroundColor('#fff1f3f5')
ForEach(this.contacts, (item: ContactData, index: number) => {
// 分组的联系人信息
ListItemGroup({ header: this.itemHead(item.initial), space: 10 }) {
ForEach(item.nameList, (it: string, i: number) => {
// 循环渲染分组A的ListItem
ListItem() {
Row({ space: 10 }) {
Image($r('app.media.ic_public_lianxiren'))
.width(40)
.fillColor(this.getRandomColor())
Text(it)
}
}
})
}
.divider({
startMargin: 60,
strokeWidth: 1,
color: '#ccc'
})
})
}
.height('100%')
.width('100%')
AlphabetIndexer({ arrayValue: this.alphabets, selected: 0 })
}
.layoutWeight(1)
}
}
@Builder
itemHead(text: string) {
// 列表分组的头部组件,对应联系人分组A、B等位置的组件
Text(text)
.fontSize(20)
.backgroundColor('#fff1f3f5')
.width('100%')
.padding(5)
}
}
3.4.2. 参考代码:
interface ContactData {
initial: string
nameList: string[]
}
@Entry
@Component
struct Page09_ContactAndAlpha {
// contacts,alphabets 不需要修改,只是用来渲染,可以不添加@State
contacts: ContactData[] = [
{ initial: 'A', nameList: ['阿猫', '阿狗', '阿虎', '阿龙', '阿鹰', '阿狼', '阿豹', '阿狮', '阿象', '阿鲸'] },
{ initial: 'B', nameList: ['白兔', '白鸽', '白鹤', '白鹭', '白狐', '白狼', '白虎', '白鹿', '白蛇', '白马'] },
{ initial: 'C', nameList: ['春花', '春风', '春雨', '春草', '春柳', '春燕', '春莺', '春蝶', '春蓝', '春绿'] },
{ initial: 'D', nameList: ['冬雪', '冬梅', '冬松', '冬竹', '冬云', '冬霜', '冬月', '冬夜', '冬青', '冬红'] },
{ initial: 'E', nameList: ['饿狼', '饿虎', '饿鹰', '饿豹', '饿熊', '饿蛇', '饿鱼', '饿虾', '饿蟹', '饿蚌'] },
{ initial: 'F', nameList: ['飞鸟', '飞鱼', '飞虫', '飞蜂', '飞蝶', '飞蛾', '飞蝉', '飞蝗', '飞鼠', '飞猫'] },
{ initial: 'G', nameList: ['孤狼', '孤鹰', '孤虎', '孤豹', '孤蛇', '孤鲨', '孤鲸', '孤鹿', '孤雁', '孤鸿'] },
{ initial: 'H', nameList: ['海鸥', '海龟', '海豚', '海星', '海马', '海葵', '海参', '海胆', '海螺', '海贝'] },
{ initial: 'I', nameList: ['火焰', '火球', '火箭', '火山', '火车', '火柴', '火把', '火鸟'] },
{ initial: 'J', nameList: ['金鱼', '金狮', '金刚', '金鹿', '金蛇', '金鹰', '金豹', '金虎', '金狐', '金猫'] },
{ initial: 'K', nameList: ['孔雀', '恐龙', '开心', '开怀', '开朗', '开拓', '开口', '开花', '开眼', '开天'] },
{ initial: 'L', nameList: ['老虎', '老鹰', '老鼠', '老狼', '老狗', '老猫', '老熊', '老鹿', '老龟', '老蛇'] },
{ initial: 'M', nameList: ['玫瑰', '牡丹', '梅花', '茉莉', '木兰', '棉花', '蜜蜂', '蚂蚁', '马蜂', '蟒蛇'] },
{ initial: 'N', nameList: ['南山', '南极', '南海', '南京', '南阳', '南风', '南瓜', '南竹', '南花', '南鸟'] },
{
initial: 'O',
nameList: ['熊猫', '欧鹭', '欧洲', '欧阳', '欧文', '欧若拉', '欧米茄', '欧罗巴', '欧菲莉亚', '欧瑞斯']
},
{ initial: 'P', nameList: ['苹果', '葡萄', '琵琶', '枇杷', '菩提', '瓢虫', '瓢泼', '飘零', '飘渺', '飘飘然'] },
{ initial: 'Q', nameList: ['七喜', '强风', '奇迹', '乾坤', '奇才', '晴天', '青竹', '秋水', '轻舞', '清泉'] },
{ initial: 'R', nameList: ['瑞雪', '瑞兽', '瑞光', '瑞云', '瑞彩', '瑞气', '瑞香', '瑞草', '瑞莲', '瑞竹'] },
{ initial: 'S', nameList: ['三羊', '三狗', '三猫', '三鱼', '三角', '三鹿', '三鹰', '三蛇', '三狐', '三豹'] },
{ initial: 'T', nameList: ['太阳', '天空', '田园', '太极', '太湖', '天鹅', '太空', '天使', '坦克', '甜橙'] },
{ initial: 'U', nameList: ['乌鸦', '乌鹊', '乌鱼', '乌龟', '乌云', '乌梅', '乌木', '乌金', '乌黑', '乌青'] },
{ initial: 'V', nameList: ['五虎', '五狼', '五鹰', '五豹', '五熊', '五蛇', '五鲨', '五鲸', '五鹿', '五马'] },
{ initial: 'W', nameList: ['悟空', '微笑', '温暖', '无畏', '温柔', '舞蹈', '问心', '悟道', '未来', '文学'] },
{ initial: 'X', nameList: ['西风', '西洋', '西子', '西施', '西岳', '西湖', '西柚', '西竹', '西花', '西鸟'] },
{ initial: 'Y', nameList: ['夜猫', '夜鹰', '夜莺', '夜空', '夜色', '夜月', '夜影', '夜翼', '夜狐', '夜狼'] },
{ initial: 'Z', nameList: ['珍珠', '紫薇', '紫霞', '紫竹', '紫云', '紫燕', '紫鸢', '紫藤', '紫荆', '紫罗兰'] },
]
alphabets: string[] = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
// 选中的索引,需要更改,且需要同步更新到 UI 上,故添加@State
@State selectedIndex: number = 0
// 随机颜色函数
getRandomColor(): ResourceColor {
// 生成 0-255 的随机数
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
// 拼接成随机的颜色,半透明并返回
return `rgba(${r}, ${g}, ${b}, 0.5)`;
}
// 控制器对象
scroller: Scroller = new Scroller()
build() {
Stack({ alignContent: Alignment.End }) {
Column() {
Stack({ alignContent: Alignment.End }) {
Text('通讯录')
.width('100%')
.textAlign(TextAlign.Center)
.fontSize(20)
.onClick(() => {
this.scroller.scrollToIndex(0, true)
})
Image($r('app.media.ic_public_add'))
.width(20)
}
.width('100%')
.padding(15)
.backgroundColor('#fff1f3f5')
List({ scroller: this.scroller }) {
// 顶部区域
ListItem() {
Row() {
Image($r('app.media.ic_public_search'))
.width(20)
.fillColor(Color.Gray)
Text('搜索')
.fontColor(Color.Gray)
}
.backgroundColor(Color.White)
.width('100%')
.height(40)
.borderRadius(5)
.justifyContent(FlexAlign.Center)
}
.padding(10)
.width('100%')
.backgroundColor('#fff1f3f5')
ForEach(this.contacts, (item: ContactData, index: number) => {
// 分组的联系人信息
ListItemGroup({ header: this.itemHead(item.initial), space: 10 }) {
ForEach(item.nameList, (it: string, i: number) => {
// 循环渲染分组A的ListItem
ListItem() {
Row({ space: 10 }) {
Image($r('app.media.ic_public_lianxiren'))
.width(40)
.fillColor(this.getRandomColor())
Text(it)
}
}
})
}
.divider({
startMargin: 60,
strokeWidth: 1,
color: '#ccc'
})
})
}
.onScrollIndex((start: number) => {
this.selectedIndex = start
})
}
AlphabetIndexer({ arrayValue: this.alphabets, selected: $$this.selectedIndex })
.usingPopup(true)
.onSelect((index: number) => {
this.scroller.scrollToIndex(index)
})
}
}
@Builder
itemHead(text: string) {
// 列表分组的头部组件,对应联系人分组A、B等位置的组件
Text(text)
.fontSize(20)
.backgroundColor('#fff1f3f5')
.width('100%')
.padding(5)
}
}
4. 案例-贝壳找房
基于今天学习的内容完成,贝壳找房案例
基础模版:
interface BKCityContent {
initial: string
cityNameList: string[]
}
@Entry
@Component
struct Page10_Demo_BK {
// 热门城市
hotCitys: string[] = ['北京', '上海', '广州', '深圳', '天津', '杭州', '南京', '苏州', '成都', '武汉', '重庆', '西安', '香港', '澳门', '台北']
// 历史城市
historyCitys: string[] = ['北京', '上海', '广州', '深圳', '重庆']
// 城市信息
cityContentList: BKCityContent[] = [
{
initial: 'A',
cityNameList: ['阿拉善', '鞍山', '安庆', '安阳', '阿坝', '安顺']
},
{
initial: 'B',
cityNameList: ['北京', '保定', '包头', '巴彦淖尔', '本溪', '白山']
},
{
initial: 'C',
cityNameList: ['成都', '重庆', '长春', '长沙', '承德', '沧州']
},
{
initial: 'D',
cityNameList: ['大连', '东莞', '大同', '丹东', '大庆', '大兴安岭']
},
{
initial: 'E',
cityNameList: ['鄂尔多斯', '鄂州', '恩施', '额尔古纳市', '二连浩特市', '恩施市']
},
{
initial: 'F',
cityNameList: ['福州', '佛山', '抚顺', '阜新', '阜阳', '抚州']
},
{
initial: 'G',
cityNameList: ['广州', '贵阳', '赣州', '桂林', '贵港', '广元']
},
{
initial: 'H',
cityNameList: ['杭州', '海口', '哈尔滨', '合肥', '呼和浩特', '邯郸']
},
{
initial: 'J',
cityNameList: ['济南', '晋城', '晋中', '锦州', '吉林', '鸡西']
},
{
initial: 'K',
cityNameList: ['昆明', '开封', '康定市', '昆山', '康保县', '宽城满族自治县']
},
{
initial: 'L',
cityNameList: ['兰州', '廊坊', '临汾', '吕梁', '辽阳', '辽源']
},
{
initial: 'M',
cityNameList: ['牡丹江', '马鞍山', '茂名', '梅州', '绵阳', '眉山']
},
{
initial: 'N',
cityNameList: ['南京', '宁波', '南昌', '南宁', '南通', '南平']
},
{
initial: 'P',
cityNameList: ['盘锦', '莆田', '萍乡', '平顶山', '濮阳', '攀枝花']
},
{
initial: 'Q',
cityNameList: ['青岛', '秦皇岛', '齐齐哈尔', '七台河', '衢州', '泉州']
},
{
initial: 'R',
cityNameList: ['日照', '日喀则', '饶阳县', '任丘市', '任泽区', '饶河县']
},
{
initial: 'S',
cityNameList: ['上海', '苏州', '深圳', '沈阳', '石家庄', '朔州']
},
{
initial: 'T',
cityNameList: ['天津', '太原', '唐山', '通辽', '铁岭', '通化']
},
{
initial: 'W',
cityNameList: ['无锡', '武汉', '乌海', '乌兰察布', '温州', '芜湖']
},
{
initial: 'X',
cityNameList: ['厦门', '西安', '西宁', '邢台', '忻州', '兴安盟']
},
{
initial: 'Y',
cityNameList: ['扬州', '阳泉', '运城', '营口', '延边', '伊春']
},
{
initial: 'Z',
cityNameList: ['郑州', '珠海', '张家口', '镇江', '舟山', '漳州']
}
]
// 右侧导航索引
alphabets: string[] = ['#', '热', "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "W", "X", "Y", "Z"]
build() {
Column() {
Image($r('app.media.ic_BK_content'))
.width('100%')
// 全屏模态的内容(测试用,完成之后删除)
this.ContentCoverBuilder()
}
.width('100%')
.height('100%')
.backgroundColor('#f8f8f8')
}
@Builder
ContentCoverBuilder() {
Stack({ alignContent: Alignment.End }) {
Column() {
// 顶部
this.TopBuilder();
// 列表
this.ListBuilder();
}
.backgroundColor(Color.White)
// 导航 写这里
this.AlphabetBuilder()
}
}
@Builder
AlphabetBuilder() {
Text()
.width(20)
.height(400)
.backgroundColor(Color.Orange)
}
@Builder
ListBuilder() {
List({ space: 30 }) {
// 历史
this.LocationListItemBuilder()
// 热门
this.HotListItemBuilder()
// A-B的区域
this.LetterListItemBuilder()
}
.divider({ startMargin: 20, endMargin: 20, color: '#f3f3f3', strokeWidth: 2 })
.width('100%')
.layoutWeight(1)
.sticky(StickyStyle.Header)
}
@Builder
LetterListItemBuilder() {
// A-B的区域
ListItemGroup({ header: this.ListItemGroupHeaderBuilder('A') }) {
ListItem() {
Text('阿拉善')
.width('100%')
.padding({ left: 20 })
}
.width('100%')
.height(50)
.backgroundColor(Color.White)
}
.padding({ bottom: 20 })
.divider({ startMargin: 20, endMargin: 20, color: '#f3f3f3', strokeWidth: 2 })
}
@Builder
ListItemGroupHeaderBuilder(title: string) {
Text(title)
.padding({ left: 20, bottom: 15, top: 20 })
.fontSize(14)
.fontColor(Color.Gray)
.backgroundColor('#f8f8f8')
.width('100%')
}
@Builder
HotListItemBuilder() {
// 热门
ListItem() {
Column({ space: 10 }) {
Text('热门城市')
.alignSelf(ItemAlign.Start)
.fontColor(Color.Gray)
.fontSize(14)
Flex({ wrap: FlexWrap.Wrap }) {
Text('北京')
.height(25)
.backgroundColor(Color.White)
.width('25%')
.margin({ bottom: 10 })
}
.padding({ left: 20, right: 20 })
}
.width('100%')
.padding({ left: 20, right: 20, bottom: 10 })
}
}
@Builder
LocationListItemBuilder() {
ListItem() {
Column({ space: 15 }) {
// 定位地址
Row() {
Text('北京')
Text() {
ImageSpan($r('app.media.ic_public_location_fill_blue'))
.width(20)
Span('开启定位')
}
}
.width('100%')
.padding({ top: 10, bottom: 10, right: 20, left: 20 })
.justifyContent(FlexAlign.SpaceBetween)
.backgroundColor(Color.White)
// 历史
Column({ space: 10 }) {
Text('历史')
.fontColor(Color.Gray)
.alignSelf(ItemAlign.Start)
.fontSize(14)
Flex({ wrap: FlexWrap.Wrap }) {
Text('北京')
.height(25)
.backgroundColor(Color.White)
.width('25%')
.margin({ bottom: 10 })
}
.padding({ left: 20, right: 20 })
}
.width('100%')
.padding({ left: 20, right: 20 })
}
}
.padding({ top: 20 })
}
@Builder
TopBuilder() {
Column() {
// X + 输入框
Row({ space: 20 }) {
Image($r('app.media.ic_public_cancel'))
.width(30)
.fillColor(Color.Gray)
Row({ space: 5 }) {
Image($r('app.media.ic_public_search'))
.width(18)
Text('请输入城市名称')
.layoutWeight(1)
}
.height(50)
.border({ width: .5, color: Color.Gray, radius: 5 })
.padding({ left: 5 })
.layoutWeight(1)
.shadow({
radius: 20,
color: '#f6f6f7'
})
}
.padding({
left: 15,
right: 15,
top: 15
})
// 国内城市
Column() {
Text('国内城市')
.fontSize(15)
.fontWeight(800)
.padding(5)
Row()
.width(20)
.height(2)
.backgroundColor('#0094ff')
.borderRadius(2)
}
}
.width('100%')
.backgroundColor(Color.White)
.height(100)
.border({
width: { bottom: 4 },
color: '#f6f6f7',
})
}
}
4.1. 全屏模态
首先来完成全屏模态
需求:
- 点击图片展示全屏模态
- 点击 x 关闭全屏模态
核心步骤:
- 点击图片展示全屏模态
- 定义状态变量,控制全屏模态显示
- 给图片绑定全屏模态,设置状态变量,及 builder
- 点击图片,修改状态变量为 true
- 点击 x 关闭全屏模态
- 给 x 绑定点击事件,状态变量改为 false
参考代码:
interface BKCityContent {
initial: string
cityNameList: string[]
}
@Entry
@Component
struct Page10_Demo_BK {
// 热门城市
hotCitys: string[] = ['北京', '上海', '广州', '深圳', '天津', '杭州', '南京', '苏州', '成都', '武汉', '重庆', '西安', '香港', '澳门', '台北']
// 历史城市
historyCitys: string[] = ['北京', '上海', '广州', '深圳', '重庆']
// 城市信息
cityContentList: BKCityContent[] = [
{
initial: 'A',
cityNameList: ['阿拉善', '鞍山', '安庆', '安阳', '阿坝', '安顺']
},
{
initial: 'B',
cityNameList: ['北京', '保定', '包头', '巴彦淖尔', '本溪', '白山']
},
{
initial: 'C',
cityNameList: ['成都', '重庆', '长春', '长沙', '承德', '沧州']
},
{
initial: 'D',
cityNameList: ['大连', '东莞', '大同', '丹东', '大庆', '大兴安岭']
},
{
initial: 'E',
cityNameList: ['鄂尔多斯', '鄂州', '恩施', '额尔古纳市', '二连浩特市', '恩施市']
},
{
initial: 'F',
cityNameList: ['福州', '佛山', '抚顺', '阜新', '阜阳', '抚州']
},
{
initial: 'G',
cityNameList: ['广州', '贵阳', '赣州', '桂林', '贵港', '广元']
},
{
initial: 'H',
cityNameList: ['杭州', '海口', '哈尔滨', '合肥', '呼和浩特', '邯郸']
},
{
initial: 'J',
cityNameList: ['济南', '晋城', '晋中', '锦州', '吉林', '鸡西']
},
{
initial: 'K',
cityNameList: ['昆明', '开封', '康定市', '昆山', '康保县', '宽城满族自治县']
},
{
initial: 'L',
cityNameList: ['兰州', '廊坊', '临汾', '吕梁', '辽阳', '辽源']
},
{
initial: 'M',
cityNameList: ['牡丹江', '马鞍山', '茂名', '梅州', '绵阳', '眉山']
},
{
initial: 'N',
cityNameList: ['南京', '宁波', '南昌', '南宁', '南通', '南平']
},
{
initial: 'P',
cityNameList: ['盘锦', '莆田', '萍乡', '平顶山', '濮阳', '攀枝花']
},
{
initial: 'Q',
cityNameList: ['青岛', '秦皇岛', '齐齐哈尔', '七台河', '衢州', '泉州']
},
{
initial: 'R',
cityNameList: ['日照', '日喀则', '饶阳县', '任丘市', '任泽区', '饶河县']
},
{
initial: 'S',
cityNameList: ['上海', '苏州', '深圳', '沈阳', '石家庄', '朔州']
},
{
initial: 'T',
cityNameList: ['天津', '太原', '唐山', '通辽', '铁岭', '通化']
},
{
initial: 'W',
cityNameList: ['无锡', '武汉', '乌海', '乌兰察布', '温州', '芜湖']
},
{
initial: 'X',
cityNameList: ['厦门', '西安', '西宁', '邢台', '忻州', '兴安盟']
},
{
initial: 'Y',
cityNameList: ['扬州', '阳泉', '运城', '营口', '延边', '伊春']
},
{
initial: 'Z',
cityNameList: ['郑州', '珠海', '张家口', '镇江', '舟山', '漳州']
}
]
// 右侧导航索引
alphabets: string[] = ['#', '热', "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "W", "X", "Y", "Z"]
// 控制全屏模态的状态变量
@State isShow: boolean = false
build() {
Column() {
Image($r('app.media.ic_BK_content'))
.width('100%')
// 绑定全屏模态
.bindContentCover(this.isShow, this.ContentCoverBuilder())
// 点击显示全屏模态
.onClick(() => {
this.isShow = true
})
}
.width('100%')
.height('100%')
.backgroundColor('#f8f8f8')
}
@Builder
ContentCoverBuilder() {
Stack({ alignContent: Alignment.End }) {
Column() {
// 顶部
this.TopBuilder();
// 列表
this.ListBuilder();
}
.backgroundColor(Color.White)
// 导航 写这里
this.AlphabetBuilder()
}
}
@Builder
AlphabetBuilder() {
Text()
.width(20)
.height(400)
.backgroundColor(Color.Orange)
}
@Builder
ListBuilder() {
List({ space: 30 }) {
// 历史
this.LocationListItemBuilder()
// 热门
this.HotListItemBuilder()
// A-B的区域
this.LetterListItemBuilder()
}
.divider({ startMargin: 20, endMargin: 20, color: '#f3f3f3', strokeWidth: 2 })
.width('100%')
.layoutWeight(1)
.sticky(StickyStyle.Header)
}
@Builder
LetterListItemBuilder() {
// A-B的区域
ListItemGroup({ header: this.ListItemGroupHeaderBuilder('A') }) {
ListItem() {
Text('阿拉善')
.width('100%')
.padding({ left: 20 })
}
.width('100%')
.height(50)
.backgroundColor(Color.White)
}
.padding({ bottom: 20 })
.divider({ startMargin: 20, endMargin: 20, color: '#f3f3f3', strokeWidth: 2 })
}
@Builder
ListItemGroupHeaderBuilder(title: string) {
Text(title)
.padding({ left: 20, bottom: 15, top: 20 })
.fontSize(14)
.fontColor(Color.Gray)
.backgroundColor('#f8f8f8')
.width('100%')
}
@Builder
HotListItemBuilder() {
// 热门
ListItem() {
Column({ space: 10 }) {
Text('热门城市')
.alignSelf(ItemAlign.Start)
.fontColor(Color.Gray)
.fontSize(14)
Flex({ wrap: FlexWrap.Wrap }) {
Text('北京')
.height(25)
.backgroundColor(Color.White)
.width('25%')
.margin({ bottom: 10 })
}
.padding({ left: 20, right: 20 })
}
.width('100%')
.padding({ left: 20, right: 20, bottom: 10 })
}
}
@Builder
LocationListItemBuilder() {
ListItem() {
Column({ space: 15 }) {
// 定位地址
Row() {
Text('北京')
Text() {
ImageSpan($r('app.media.ic_public_location_fill_blue'))
.width(20)
Span('开启定位')
}
}
.width('100%')
.padding({ top: 10, bottom: 10, right: 20, left: 20 })
.justifyContent(FlexAlign.SpaceBetween)
.backgroundColor(Color.White)
// 历史
Column({ space: 10 }) {
Text('历史')
.fontColor(Color.Gray)
.alignSelf(ItemAlign.Start)
.fontSize(14)
Flex({ wrap: FlexWrap.Wrap }) {
Text('北京')
.height(25)
.backgroundColor(Color.White)
.width('25%')
.margin({ bottom: 10 })
}
.padding({ left: 20, right: 20 })
}
.width('100%')
.padding({ left: 20, right: 20 })
}
}
.padding({ top: 20 })
}
@Builder
TopBuilder() {
Column() {
// X + 输入框
Row({ space: 20 }) {
Image($r('app.media.ic_public_cancel'))
.width(30)
.fillColor(Color.Gray)
// 点击关闭全屏模态
.onClick(() => {
this.isShow = false
})
Row({ space: 5 }) {
Image($r('app.media.ic_public_search'))
.width(18)
Text('请输入城市名称')
.layoutWeight(1)
}
.height(50)
.border({ width: .5, color: Color.Gray, radius: 5 })
.padding({ left: 5 })
.layoutWeight(1)
.shadow({
radius: 20,
color: '#f6f6f7'
})
}
.padding({
left: 15,
right: 15,
top: 15
})
// 国内城市
Column() {
Text('国内城市')
.fontSize(15)
.fontWeight(800)
.padding(5)
Row()
.width(20)
.height(2)
.backgroundColor('#0094ff')
.borderRadius(2)
}
}
.width('100%')
.backgroundColor(Color.White)
.height(100)
.border({
width: { bottom: 4 },
color: '#f6f6f7',
})
}
}
4.2. 渲染历史和热门城市
接下来将历史和热门城市渲染出来
核心步骤:
- 确认数据及结构
- ForEach出结果
interface BKCityContent {
initial: string
cityNameList: string[]
}
@Entry
@Component
struct Page10_Demo_BK {
// 热门城市
hotCitys: string[] = ['北京', '上海', '广州', '深圳', '天津', '杭州', '南京', '苏州', '成都', '武汉', '重庆', '西安', '香港', '澳门', '台北']
// 历史城市
historyCitys: string[] = ['北京', '上海', '广州', '深圳', '重庆']
// 城市信息
cityContentList: BKCityContent[] = [
{
initial: 'A',
cityNameList: ['阿拉善', '鞍山', '安庆', '安阳', '阿坝', '安顺']
},
{
initial: 'B',
cityNameList: ['北京', '保定', '包头', '巴彦淖尔', '本溪', '白山']
},
{
initial: 'C',
cityNameList: ['成都', '重庆', '长春', '长沙', '承德', '沧州']
},
{
initial: 'D',
cityNameList: ['大连', '东莞', '大同', '丹东', '大庆', '大兴安岭']
},
{
initial: 'E',
cityNameList: ['鄂尔多斯', '鄂州', '恩施', '额尔古纳市', '二连浩特市', '恩施市']
},
{
initial: 'F',
cityNameList: ['福州', '佛山', '抚顺', '阜新', '阜阳', '抚州']
},
{
initial: 'G',
cityNameList: ['广州', '贵阳', '赣州', '桂林', '贵港', '广元']
},
{
initial: 'H',
cityNameList: ['杭州', '海口', '哈尔滨', '合肥', '呼和浩特', '邯郸']
},
{
initial: 'J',
cityNameList: ['济南', '晋城', '晋中', '锦州', '吉林', '鸡西']
},
{
initial: 'K',
cityNameList: ['昆明', '开封', '康定市', '昆山', '康保县', '宽城满族自治县']
},
{
initial: 'L',
cityNameList: ['兰州', '廊坊', '临汾', '吕梁', '辽阳', '辽源']
},
{
initial: 'M',
cityNameList: ['牡丹江', '马鞍山', '茂名', '梅州', '绵阳', '眉山']
},
{
initial: 'N',
cityNameList: ['南京', '宁波', '南昌', '南宁', '南通', '南平']
},
{
initial: 'P',
cityNameList: ['盘锦', '莆田', '萍乡', '平顶山', '濮阳', '攀枝花']
},
{
initial: 'Q',
cityNameList: ['青岛', '秦皇岛', '齐齐哈尔', '七台河', '衢州', '泉州']
},
{
initial: 'R',
cityNameList: ['日照', '日喀则', '饶阳县', '任丘市', '任泽区', '饶河县']
},
{
initial: 'S',
cityNameList: ['上海', '苏州', '深圳', '沈阳', '石家庄', '朔州']
},
{
initial: 'T',
cityNameList: ['天津', '太原', '唐山', '通辽', '铁岭', '通化']
},
{
initial: 'W',
cityNameList: ['无锡', '武汉', '乌海', '乌兰察布', '温州', '芜湖']
},
{
initial: 'X',
cityNameList: ['厦门', '西安', '西宁', '邢台', '忻州', '兴安盟']
},
{
initial: 'Y',
cityNameList: ['扬州', '阳泉', '运城', '营口', '延边', '伊春']
},
{
initial: 'Z',
cityNameList: ['郑州', '珠海', '张家口', '镇江', '舟山', '漳州']
}
]
// 右侧导航索引
alphabets: string[] = ['#', '热', "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "W", "X", "Y", "Z"]
// 控制全屏模态的状态变量
@State isShow: boolean = false
build() {
Column() {
Image($r('app.media.ic_BK_content'))
.width('100%')// 绑定全屏模态
.bindContentCover(this.isShow, this.ContentCoverBuilder())// 点击显示全屏模态
.onClick(() => {
this.isShow = true
})
}
.width('100%')
.height('100%')
.backgroundColor('#f8f8f8')
}
@Builder
ContentCoverBuilder() {
Stack({ alignContent: Alignment.End }) {
Column() {
// 顶部
this.TopBuilder();
// 列表
this.ListBuilder();
}
.backgroundColor(Color.White)
// 导航 写这里
this.AlphabetBuilder()
}
}
@Builder
AlphabetBuilder() {
Text()
.width(20)
.height(400)
.backgroundColor(Color.Orange)
}
@Builder
ListBuilder() {
List({ space: 30 }) {
// 历史
this.LocationListItemBuilder()
// 热门
this.HotListItemBuilder()
// A-B的区域
this.LetterListItemBuilder()
}
.divider({ startMargin: 20, endMargin: 20, color: '#f3f3f3', strokeWidth: 2 })
.width('100%')
.layoutWeight(1)
.sticky(StickyStyle.Header)
}
@Builder
LetterListItemBuilder() {
// A-B的区域
ListItemGroup({ header: this.ListItemGroupHeaderBuilder('A') }) {
ListItem() {
Text('阿拉善')
.width('100%')
.padding({ left: 20 })
}
.width('100%')
.height(50)
.backgroundColor(Color.White)
}
.padding({ bottom: 20 })
.divider({ startMargin: 20, endMargin: 20, color: '#f3f3f3', strokeWidth: 2 })
}
@Builder
ListItemGroupHeaderBuilder(title: string) {
Text(title)
.padding({ left: 20, bottom: 15, top: 20 })
.fontSize(14)
.fontColor(Color.Gray)
.backgroundColor('#f8f8f8')
.width('100%')
}
@Builder
HotListItemBuilder() {
// 热门
ListItem() {
Column({ space: 10 }) {
Text('热门城市')
.alignSelf(ItemAlign.Start)
.fontColor(Color.Gray)
.fontSize(14)
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.hotCitys, (item: string, index: number) => {
Text(item)
.height(25)
.backgroundColor(Color.White)
.width('25%')
.margin({ bottom: 10 })
})
}
.padding({ left: 20, right: 20 })
}
.width('100%')
.padding({ left: 20, right: 20, bottom: 10 })
}
}
@Builder
LocationListItemBuilder() {
ListItem() {
Column({ space: 15 }) {
// 定位地址
Row() {
Text('北京')
Text() {
ImageSpan($r('app.media.ic_public_location_fill_blue'))
.width(20)
Span('开启定位')
}
}
.width('100%')
.padding({ top: 10, bottom: 10, right: 20, left: 20 })
.justifyContent(FlexAlign.SpaceBetween)
.backgroundColor(Color.White)
// 历史
Column({ space: 10 }) {
Text('历史')
.fontColor(Color.Gray)
.alignSelf(ItemAlign.Start)
.fontSize(14)
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.historyCitys, (item: string, index: number) => {
Text(item)
.height(25)
.backgroundColor(Color.White)
.width('25%')
.margin({ bottom: 10 })
})
}
.padding({ left: 20, right: 20 })
}
.width('100%')
.padding({ left: 20, right: 20 })
}
}
.padding({ top: 20 })
}
@Builder
TopBuilder() {
Column() {
// X + 输入框
Row({ space: 20 }) {
Image($r('app.media.ic_public_cancel'))
.width(30)
.fillColor(Color.Gray)// 点击关闭全屏模态
.onClick(() => {
this.isShow = false
})
Row({ space: 5 }) {
Image($r('app.media.ic_public_search'))
.width(18)
Text('请输入城市名称')
.layoutWeight(1)
}
.height(50)
.border({ width: .5, color: Color.Gray, radius: 5 })
.padding({ left: 5 })
.layoutWeight(1)
.shadow({
radius: 20,
color: '#f6f6f7'
})
}
.padding({
left: 15,
right: 15,
top: 15
})
// 国内城市
Column() {
Text('国内城市')
.fontSize(15)
.fontWeight(800)
.padding(5)
Row()
.width(20)
.height(2)
.backgroundColor('#0094ff')
.borderRadius(2)
}
}
.width('100%')
.backgroundColor(Color.White)
.height(100)
.border({
width: { bottom: 4 },
color: '#f6f6f7',
})
}
}
4.3. 渲染城市列表
核心步骤:
- 明确数据结构
- 通过嵌套循环完成渲染
参考代码:
interface BKCityContent {
initial: string
cityNameList: string[]
}
@Entry
@Component
struct Page10_Demo_BK {
// 热门城市
hotCitys: string[] = ['北京', '上海', '广州', '深圳', '天津', '杭州', '南京', '苏州', '成都', '武汉', '重庆', '西安', '香港', '澳门', '台北']
// 历史城市
historyCitys: string[] = ['北京', '上海', '广州', '深圳', '重庆']
// 城市信息
cityContentList: BKCityContent[] = [
{
initial: 'A',
cityNameList: ['阿拉善', '鞍山', '安庆', '安阳', '阿坝', '安顺']
},
{
initial: 'B',
cityNameList: ['北京', '保定', '包头', '巴彦淖尔', '本溪', '白山']
},
{
initial: 'C',
cityNameList: ['成都', '重庆', '长春', '长沙', '承德', '沧州']
},
{
initial: 'D',
cityNameList: ['大连', '东莞', '大同', '丹东', '大庆', '大兴安岭']
},
{
initial: 'E',
cityNameList: ['鄂尔多斯', '鄂州', '恩施', '额尔古纳市', '二连浩特市', '恩施市']
},
{
initial: 'F',
cityNameList: ['福州', '佛山', '抚顺', '阜新', '阜阳', '抚州']
},
{
initial: 'G',
cityNameList: ['广州', '贵阳', '赣州', '桂林', '贵港', '广元']
},
{
initial: 'H',
cityNameList: ['杭州', '海口', '哈尔滨', '合肥', '呼和浩特', '邯郸']
},
{
initial: 'J',
cityNameList: ['济南', '晋城', '晋中', '锦州', '吉林', '鸡西']
},
{
initial: 'K',
cityNameList: ['昆明', '开封', '康定市', '昆山', '康保县', '宽城满族自治县']
},
{
initial: 'L',
cityNameList: ['兰州', '廊坊', '临汾', '吕梁', '辽阳', '辽源']
},
{
initial: 'M',
cityNameList: ['牡丹江', '马鞍山', '茂名', '梅州', '绵阳', '眉山']
},
{
initial: 'N',
cityNameList: ['南京', '宁波', '南昌', '南宁', '南通', '南平']
},
{
initial: 'P',
cityNameList: ['盘锦', '莆田', '萍乡', '平顶山', '濮阳', '攀枝花']
},
{
initial: 'Q',
cityNameList: ['青岛', '秦皇岛', '齐齐哈尔', '七台河', '衢州', '泉州']
},
{
initial: 'R',
cityNameList: ['日照', '日喀则', '饶阳县', '任丘市', '任泽区', '饶河县']
},
{
initial: 'S',
cityNameList: ['上海', '苏州', '深圳', '沈阳', '石家庄', '朔州']
},
{
initial: 'T',
cityNameList: ['天津', '太原', '唐山', '通辽', '铁岭', '通化']
},
{
initial: 'W',
cityNameList: ['无锡', '武汉', '乌海', '乌兰察布', '温州', '芜湖']
},
{
initial: 'X',
cityNameList: ['厦门', '西安', '西宁', '邢台', '忻州', '兴安盟']
},
{
initial: 'Y',
cityNameList: ['扬州', '阳泉', '运城', '营口', '延边', '伊春']
},
{
initial: 'Z',
cityNameList: ['郑州', '珠海', '张家口', '镇江', '舟山', '漳州']
}
]
// 右侧导航索引
alphabets: string[] = ['#', '热', "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "W", "X", "Y", "Z"]
// 控制全屏模态的状态变量
@State isShow: boolean = false
build() {
Column() {
Image($r('app.media.ic_BK_content'))
.width('100%')// 绑定全屏模态
.bindContentCover(this.isShow, this.ContentCoverBuilder())// 点击显示全屏模态
.onClick(() => {
this.isShow = true
})
}
.width('100%')
.height('100%')
.backgroundColor('#f8f8f8')
}
@Builder
ContentCoverBuilder() {
Stack({ alignContent: Alignment.End }) {
Column() {
// 顶部
this.TopBuilder();
// 列表
this.ListBuilder();
}
.backgroundColor(Color.White)
// 导航 写这里
this.AlphabetBuilder()
}
}
@Builder
AlphabetBuilder() {
Text()
.width(20)
.height(400)
.backgroundColor(Color.Orange)
}
@Builder
ListBuilder() {
List({ space: 30 }) {
// 历史
this.LocationListItemBuilder()
// 热门
this.HotListItemBuilder()
// A-B的区域
this.LetterListItemBuilder()
}
.divider({ startMargin: 20, endMargin: 20, color: '#f3f3f3', strokeWidth: 2 })
.width('100%')
.layoutWeight(1)
.sticky(StickyStyle.Header)
}
@Builder
LetterListItemBuilder() {
// 外层循环
ForEach(this.cityContentList, (item: BKCityContent, index: number) => {
// A-B的区域
ListItemGroup({ header: this.ListItemGroupHeaderBuilder(item.initial) }) {
// 内层循环
ForEach(item.cityNameList, (it: string, index: number) => {
ListItem() {
Text(it)
.width('100%')
.padding({ left: 20 })
}
.width('100%')
.height(50)
.backgroundColor(Color.White)
})
}
.padding({ bottom: 20 })
.divider({ startMargin: 20, endMargin: 20, color: '#f3f3f3', strokeWidth: 2 })
})
}
@Builder
ListItemGroupHeaderBuilder(title: string) {
Text(title)
.padding({ left: 20, bottom: 15, top: 20 })
.fontSize(14)
.fontColor(Color.Gray)
.backgroundColor('#f8f8f8')
.width('100%')
}
@Builder
HotListItemBuilder() {
// 热门
ListItem() {
Column({ space: 10 }) {
Text('热门城市')
.alignSelf(ItemAlign.Start)
.fontColor(Color.Gray)
.fontSize(14)
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.hotCitys, (item: string, index: number) => {
Text(item)
.height(25)
.backgroundColor(Color.White)
.width('25%')
.margin({ bottom: 10 })
})
}
.padding({ left: 20, right: 20 })
}
.width('100%')
.padding({ left: 20, right: 20, bottom: 10 })
}
}
@Builder
LocationListItemBuilder() {
ListItem() {
Column({ space: 15 }) {
// 定位地址
Row() {
Text('北京')
Text() {
ImageSpan($r('app.media.ic_public_location_fill_blue'))
.width(20)
Span('开启定位')
}
}
.width('100%')
.padding({ top: 10, bottom: 10, right: 20, left: 20 })
.justifyContent(FlexAlign.SpaceBetween)
.backgroundColor(Color.White)
// 历史
Column({ space: 10 }) {
Text('历史')
.fontColor(Color.Gray)
.alignSelf(ItemAlign.Start)
.fontSize(14)
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.historyCitys, (item: string, index: number) => {
Text(item)
.height(25)
.backgroundColor(Color.White)
.width('25%')
.margin({ bottom: 10 })
})
}
.padding({ left: 20, right: 20 })
}
.width('100%')
.padding({ left: 20, right: 20 })
}
}
.padding({ top: 20 })
}
@Builder
TopBuilder() {
Column() {
// X + 输入框
Row({ space: 20 }) {
Image($r('app.media.ic_public_cancel'))
.width(30)
.fillColor(Color.Gray)// 点击关闭全屏模态
.onClick(() => {
this.isShow = false
})
Row({ space: 5 }) {
Image($r('app.media.ic_public_search'))
.width(18)
Text('请输入城市名称')
.layoutWeight(1)
}
.height(50)
.border({ width: .5, color: Color.Gray, radius: 5 })
.padding({ left: 5 })
.layoutWeight(1)
.shadow({
radius: 20,
color: '#f6f6f7'
})
}
.padding({
left: 15,
right: 15,
top: 15
})
// 国内城市
Column() {
Text('国内城市')
.fontSize(15)
.fontWeight(800)
.padding(5)
Row()
.width(20)
.height(2)
.backgroundColor('#0094ff')
.borderRadius(2)
}
}
.width('100%')
.backgroundColor(Color.White)
.height(100)
.border({
width: { bottom: 4 },
color: '#f6f6f7',
})
}
}
4.4. 渲染侧边导航
然后渲染侧边的 AlphabetIndexer 组件
核心步骤:
- 确认数据及结构
- 设置 AlphabetIndexer 组件并通过双向绑定关联索引
interface BKCityContent {
initial: string
cityNameList: string[]
}
@Entry
@Component
struct Page10_Demo_BK {
// 热门城市
hotCitys: string[] = ['北京', '上海', '广州', '深圳', '天津', '杭州', '南京', '苏州', '成都', '武汉', '重庆', '西安', '香港', '澳门', '台北']
// 历史城市
historyCitys: string[] = ['北京', '上海', '广州', '深圳', '重庆']
// 城市信息
cityContentList: BKCityContent[] = [
{
initial: 'A',
cityNameList: ['阿拉善', '鞍山', '安庆', '安阳', '阿坝', '安顺']
},
{
initial: 'B',
cityNameList: ['北京', '保定', '包头', '巴彦淖尔', '本溪', '白山']
},
{
initial: 'C',
cityNameList: ['成都', '重庆', '长春', '长沙', '承德', '沧州']
},
{
initial: 'D',
cityNameList: ['大连', '东莞', '大同', '丹东', '大庆', '大兴安岭']
},
{
initial: 'E',
cityNameList: ['鄂尔多斯', '鄂州', '恩施', '额尔古纳市', '二连浩特市', '恩施市']
},
{
initial: 'F',
cityNameList: ['福州', '佛山', '抚顺', '阜新', '阜阳', '抚州']
},
{
initial: 'G',
cityNameList: ['广州', '贵阳', '赣州', '桂林', '贵港', '广元']
},
{
initial: 'H',
cityNameList: ['杭州', '海口', '哈尔滨', '合肥', '呼和浩特', '邯郸']
},
{
initial: 'J',
cityNameList: ['济南', '晋城', '晋中', '锦州', '吉林', '鸡西']
},
{
initial: 'K',
cityNameList: ['昆明', '开封', '康定市', '昆山', '康保县', '宽城满族自治县']
},
{
initial: 'L',
cityNameList: ['兰州', '廊坊', '临汾', '吕梁', '辽阳', '辽源']
},
{
initial: 'M',
cityNameList: ['牡丹江', '马鞍山', '茂名', '梅州', '绵阳', '眉山']
},
{
initial: 'N',
cityNameList: ['南京', '宁波', '南昌', '南宁', '南通', '南平']
},
{
initial: 'P',
cityNameList: ['盘锦', '莆田', '萍乡', '平顶山', '濮阳', '攀枝花']
},
{
initial: 'Q',
cityNameList: ['青岛', '秦皇岛', '齐齐哈尔', '七台河', '衢州', '泉州']
},
{
initial: 'R',
cityNameList: ['日照', '日喀则', '饶阳县', '任丘市', '任泽区', '饶河县']
},
{
initial: 'S',
cityNameList: ['上海', '苏州', '深圳', '沈阳', '石家庄', '朔州']
},
{
initial: 'T',
cityNameList: ['天津', '太原', '唐山', '通辽', '铁岭', '通化']
},
{
initial: 'W',
cityNameList: ['无锡', '武汉', '乌海', '乌兰察布', '温州', '芜湖']
},
{
initial: 'X',
cityNameList: ['厦门', '西安', '西宁', '邢台', '忻州', '兴安盟']
},
{
initial: 'Y',
cityNameList: ['扬州', '阳泉', '运城', '营口', '延边', '伊春']
},
{
initial: 'Z',
cityNameList: ['郑州', '珠海', '张家口', '镇江', '舟山', '漳州']
}
]
// 右侧导航索引
alphabets: string[] = ['#', '热', "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "W", "X", "Y", "Z"]
// 控制全屏模态的状态变量
@State isShow: boolean = false
// 选中的索引
@State selectedIndex: number = 0
build() {
Column() {
Image($r('app.media.ic_BK_content'))
.width('100%')// 绑定全屏模态
.bindContentCover(this.isShow, this.ContentCoverBuilder())// 点击显示全屏模态
.onClick(() => {
this.isShow = true
})
}
.width('100%')
.height('100%')
.backgroundColor('#f8f8f8')
}
@Builder
ContentCoverBuilder() {
Stack({ alignContent: Alignment.End }) {
Column() {
// 顶部
this.TopBuilder();
// 列表
this.ListBuilder();
}
.backgroundColor(Color.White)
// 导航 写这里
this.AlphabetBuilder()
}
}
@Builder
AlphabetBuilder() {
// 添加导航组件
AlphabetIndexer({ arrayValue: this.alphabets, selected: $$this.selectedIndex })
}
@Builder
ListBuilder() {
List({ space: 30 }) {
// 历史
this.LocationListItemBuilder()
// 热门
this.HotListItemBuilder()
// A-B的区域
this.LetterListItemBuilder()
}
.divider({ startMargin: 20, endMargin: 20, color: '#f3f3f3', strokeWidth: 2 })
.width('100%')
.layoutWeight(1)
.sticky(StickyStyle.Header)
}
@Builder
LetterListItemBuilder() {
// 外层循环
ForEach(this.cityContentList, (item: BKCityContent, index: number) => {
// A-B的区域
ListItemGroup({ header: this.ListItemGroupHeaderBuilder(item.initial) }) {
// 内层循环
ForEach(item.cityNameList, (it: string, index: number) => {
ListItem() {
Text(it)
.width('100%')
.padding({ left: 20 })
}
.width('100%')
.height(50)
.backgroundColor(Color.White)
})
}
.padding({ bottom: 20 })
.divider({ startMargin: 20, endMargin: 20, color: '#f3f3f3', strokeWidth: 2 })
})
}
@Builder
ListItemGroupHeaderBuilder(title: string) {
Text(title)
.padding({ left: 20, bottom: 15, top: 20 })
.fontSize(14)
.fontColor(Color.Gray)
.backgroundColor('#f8f8f8')
.width('100%')
}
@Builder
HotListItemBuilder() {
// 热门
ListItem() {
Column({ space: 10 }) {
Text('热门城市')
.alignSelf(ItemAlign.Start)
.fontColor(Color.Gray)
.fontSize(14)
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.hotCitys, (item: string, index: number) => {
Text(item)
.height(25)
.backgroundColor(Color.White)
.width('25%')
.margin({ bottom: 10 })
})
}
.padding({ left: 20, right: 20 })
}
.width('100%')
.padding({ left: 20, right: 20, bottom: 10 })
}
}
@Builder
LocationListItemBuilder() {
ListItem() {
Column({ space: 15 }) {
// 定位地址
Row() {
Text('北京')
Text() {
ImageSpan($r('app.media.ic_public_location_fill_blue'))
.width(20)
Span('开启定位')
}
}
.width('100%')
.padding({ top: 10, bottom: 10, right: 20, left: 20 })
.justifyContent(FlexAlign.SpaceBetween)
.backgroundColor(Color.White)
// 历史
Column({ space: 10 }) {
Text('历史')
.fontColor(Color.Gray)
.alignSelf(ItemAlign.Start)
.fontSize(14)
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.historyCitys, (item: string, index: number) => {
Text(item)
.height(25)
.backgroundColor(Color.White)
.width('25%')
.margin({ bottom: 10 })
})
}
.padding({ left: 20, right: 20 })
}
.width('100%')
.padding({ left: 20, right: 20 })
}
}
.padding({ top: 20 })
}
@Builder
TopBuilder() {
Column() {
// X + 输入框
Row({ space: 20 }) {
Image($r('app.media.ic_public_cancel'))
.width(30)
.fillColor(Color.Gray)// 点击关闭全屏模态
.onClick(() => {
this.isShow = false
})
Row({ space: 5 }) {
Image($r('app.media.ic_public_search'))
.width(18)
Text('请输入城市名称')
.layoutWeight(1)
}
.height(50)
.border({ width: .5, color: Color.Gray, radius: 5 })
.padding({ left: 5 })
.layoutWeight(1)
.shadow({
radius: 20,
color: '#f6f6f7'
})
}
.padding({
left: 15,
right: 15,
top: 15
})
// 国内城市
Column() {
Text('国内城市')
.fontSize(15)
.fontWeight(800)
.padding(5)
Row()
.width(20)
.height(2)
.backgroundColor('#0094ff')
.borderRadius(2)
}
}
.width('100%')
.backgroundColor(Color.White)
.height(100)
.border({
width: { bottom: 4 },
color: '#f6f6f7',
})
}
}
4.5. 城市及导航的联动效果
最后完成联动效果
核心步骤:
- 滚动 List,同步选中对应的AlphabetIndexer
- List 组件注册 onScrollIndex 事件,事件中获取索引
- 将获取到的索引设置给 AlphabetIndexer 绑定的索引值
- 选中AlphabetIndexer的区域,同步滚动List
- 创建控制器对象
- 控制器对象设置给 List 组件
- AlphabetIndexer 组件添加 onSelect 事件
- 获取索引值
- 调用控制器对象的 scrollToIndex方法,结合索引滚动列表
参考代码
interface BKCityContent {
initial: string
cityNameList: string[]
}
@Entry
@Component
struct Page10_Demo_BK {
// 热门城市
hotCitys: string[] = ['北京', '上海', '广州', '深圳', '天津', '杭州', '南京', '苏州', '成都', '武汉', '重庆', '西安', '香港', '澳门', '台北']
// 历史城市
historyCitys: string[] = ['北京', '上海', '广州', '深圳', '重庆']
// 城市信息
cityContentList: BKCityContent[] = [
{
initial: 'A',
cityNameList: ['阿拉善', '鞍山', '安庆', '安阳', '阿坝', '安顺']
},
{
initial: 'B',
cityNameList: ['北京', '保定', '包头', '巴彦淖尔', '本溪', '白山']
},
{
initial: 'C',
cityNameList: ['成都', '重庆', '长春', '长沙', '承德', '沧州']
},
{
initial: 'D',
cityNameList: ['大连', '东莞', '大同', '丹东', '大庆', '大兴安岭']
},
{
initial: 'E',
cityNameList: ['鄂尔多斯', '鄂州', '恩施', '额尔古纳市', '二连浩特市', '恩施市']
},
{
initial: 'F',
cityNameList: ['福州', '佛山', '抚顺', '阜新', '阜阳', '抚州']
},
{
initial: 'G',
cityNameList: ['广州', '贵阳', '赣州', '桂林', '贵港', '广元']
},
{
initial: 'H',
cityNameList: ['杭州', '海口', '哈尔滨', '合肥', '呼和浩特', '邯郸']
},
{
initial: 'J',
cityNameList: ['济南', '晋城', '晋中', '锦州', '吉林', '鸡西']
},
{
initial: 'K',
cityNameList: ['昆明', '开封', '康定市', '昆山', '康保县', '宽城满族自治县']
},
{
initial: 'L',
cityNameList: ['兰州', '廊坊', '临汾', '吕梁', '辽阳', '辽源']
},
{
initial: 'M',
cityNameList: ['牡丹江', '马鞍山', '茂名', '梅州', '绵阳', '眉山']
},
{
initial: 'N',
cityNameList: ['南京', '宁波', '南昌', '南宁', '南通', '南平']
},
{
initial: 'P',
cityNameList: ['盘锦', '莆田', '萍乡', '平顶山', '濮阳', '攀枝花']
},
{
initial: 'Q',
cityNameList: ['青岛', '秦皇岛', '齐齐哈尔', '七台河', '衢州', '泉州']
},
{
initial: 'R',
cityNameList: ['日照', '日喀则', '饶阳县', '任丘市', '任泽区', '饶河县']
},
{
initial: 'S',
cityNameList: ['上海', '苏州', '深圳', '沈阳', '石家庄', '朔州']
},
{
initial: 'T',
cityNameList: ['天津', '太原', '唐山', '通辽', '铁岭', '通化']
},
{
initial: 'W',
cityNameList: ['无锡', '武汉', '乌海', '乌兰察布', '温州', '芜湖']
},
{
initial: 'X',
cityNameList: ['厦门', '西安', '西宁', '邢台', '忻州', '兴安盟']
},
{
initial: 'Y',
cityNameList: ['扬州', '阳泉', '运城', '营口', '延边', '伊春']
},
{
initial: 'Z',
cityNameList: ['郑州', '珠海', '张家口', '镇江', '舟山', '漳州']
}
]
// 右侧导航索引
alphabets: string[] = ['#', '热', "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "W", "X", "Y", "Z"]
// 控制全屏模态的状态变量
@State isShow: boolean = false
// 选中的索引
@State selectedIndex: number = 0
// 控制器对象,控制 List 滚动,无需添加@State
scroller: Scroller = new Scroller()
build() {
Column() {
Image($r('app.media.ic_BK_content'))
.width('100%')// 绑定全屏模态
.bindContentCover(this.isShow, this.ContentCoverBuilder())// 点击显示全屏模态
.onClick(() => {
this.isShow = true
})
}
.width('100%')
.height('100%')
.backgroundColor('#f8f8f8')
}
@Builder
ContentCoverBuilder() {
Stack({ alignContent: Alignment.End }) {
Column() {
// 顶部
this.TopBuilder();
// 列表
this.ListBuilder();
}
.backgroundColor(Color.White)
// 导航 写这里
this.AlphabetBuilder()
}
}
@Builder
AlphabetBuilder() {
// 添加导航组件
AlphabetIndexer({ arrayValue: this.alphabets, selected: $$this.selectedIndex })
// 选中不同导航点,同步滚动 List
.onSelect((index: number) => {
this.scroller.scrollToIndex(index)
})
}
@Builder
ListBuilder() {
// 绑定控制器对象
List({ space: 30, scroller: this.scroller }) {
// 历史 索引 0
this.LocationListItemBuilder()
// 热门 索引 1
this.HotListItemBuilder()
// A-B的区域 索引 2-n
this.LetterListItemBuilder()
}
.divider({ startMargin: 20, endMargin: 20, color: '#f3f3f3', strokeWidth: 2 })
.width('100%')
.layoutWeight(1)
.sticky(StickyStyle.Header)
// 滚动 List 同步更新导航
.onScrollIndex((start: number) => {
this.selectedIndex = start
})
}
@Builder
LetterListItemBuilder() {
// 外层循环
ForEach(this.cityContentList, (item: BKCityContent, index: number) => {
// A-B的区域
ListItemGroup({ header: this.ListItemGroupHeaderBuilder(item.initial) }) {
// 内层循环
ForEach(item.cityNameList, (it: string, index: number) => {
ListItem() {
Text(it)
.width('100%')
.padding({ left: 20 })
}
.width('100%')
.height(50)
.backgroundColor(Color.White)
})
}
.padding({ bottom: 20 })
.divider({ startMargin: 20, endMargin: 20, color: '#f3f3f3', strokeWidth: 2 })
})
}
@Builder
ListItemGroupHeaderBuilder(title: string) {
Text(title)
.padding({ left: 20, bottom: 15, top: 20 })
.fontSize(14)
.fontColor(Color.Gray)
.backgroundColor('#f8f8f8')
.width('100%')
}
@Builder
HotListItemBuilder() {
// 热门
ListItem() {
Column({ space: 10 }) {
Text('热门城市')
.alignSelf(ItemAlign.Start)
.fontColor(Color.Gray)
.fontSize(14)
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.hotCitys, (item: string, index: number) => {
Text(item)
.height(25)
.backgroundColor(Color.White)
.width('25%')
.margin({ bottom: 10 })
})
}
.padding({ left: 20, right: 20 })
}
.width('100%')
.padding({ left: 20, right: 20, bottom: 10 })
}
}
@Builder
LocationListItemBuilder() {
ListItem() {
Column({ space: 15 }) {
// 定位地址
Row() {
Text('北京')
Text() {
ImageSpan($r('app.media.ic_public_location_fill_blue'))
.width(20)
Span('开启定位')
}
}
.width('100%')
.padding({ top: 10, bottom: 10, right: 20, left: 20 })
.justifyContent(FlexAlign.SpaceBetween)
.backgroundColor(Color.White)
// 历史
Column({ space: 10 }) {
Text('历史')
.fontColor(Color.Gray)
.alignSelf(ItemAlign.Start)
.fontSize(14)
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.historyCitys, (item: string, index: number) => {
Text(item)
.height(25)
.backgroundColor(Color.White)
.width('25%')
.margin({ bottom: 10 })
})
}
.padding({ left: 20, right: 20 })
}
.width('100%')
.padding({ left: 20, right: 20 })
}
}
.padding({ top: 20 })
}
@Builder
TopBuilder() {
Column() {
// X + 输入框
Row({ space: 20 }) {
Image($r('app.media.ic_public_cancel'))
.width(30)
.fillColor(Color.Gray)// 点击关闭全屏模态
.onClick(() => {
this.isShow = false
})
Row({ space: 5 }) {
Image($r('app.media.ic_public_search'))
.width(18)
Text('请输入城市名称')
.layoutWeight(1)
}
.height(50)
.border({ width: .5, color: Color.Gray, radius: 5 })
.padding({ left: 5 })
.layoutWeight(1)
.shadow({
radius: 20,
color: '#f6f6f7'
})
}
.padding({
left: 15,
right: 15,
top: 15
})
// 国内城市
Column() {
Text('国内城市')
.fontSize(15)
.fontWeight(800)
.padding(5)
Row()
.width(20)
.height(2)
.backgroundColor('#0094ff')
.borderRadius(2)
}
}
.width('100%')
.backgroundColor(Color.White)
.height(100)
.border({
width: { bottom: 4 },
color: '#f6f6f7',
})
}
}