技术点拆分
1、List列表;
2、AlphabetIndexer侧栏索引联动;
3、swipeAction侧滑删除;
4、Badge消息提示;
效果展示
List列表
通讯录中数据不是很多,因此List使用ForEach即可,使用ListItemGroup对列表进行分组,然后设置下划线等样式。
private listScroller: Scroller = new Scroller();
List({ scroller: this.listScroller }){
ListItemGroup() {
ListItem(){
ContactsRow({imagePath:'/images/contact_0.png',titleString:'新的朋友'})
}
ListItem(){
ContactsRow({imagePath:'/images/contact_1.png',titleString:'仅聊天的朋友'})
}
ListItem(){
ContactsRow({imagePath:'/images/contact_2.png',titleString:'群聊'})
}
ListItem(){
ContactsRow({imagePath:'/images/contact_3.png',titleString:'标签'})
}
ListItem(){
ContactsRow({imagePath:'/images/contact_4.png',titleString:'公众号'})
}
}
.padding({left:15,right:15})
.divider({ strokeWidth: 2, color: 'rgb(247,247,247)', startMargin: 60, endMargin: 0 })
ListItemGroup() {
ListItem(){
Column() {
Column() {
Text('我的企业及企业联系人').fontSize(14).fontColor(Color.Gray)
}
.backgroundColor('#E6E6E6')
.width('100%')
.height(36)
.padding({
left: 15
})
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Start)
ContactsRow({
imagePath:'/images/contact_5.png',
titleString:'企业微信联系人'
}).padding({left:15,right:15})
}
}
ListItem(){
ContactsRow({
imagePath:'/images/contact_5.png',
titleString:'企业微信通知',
count: 36
}).padding({left:15,right:15})
}
}
.divider({ strokeWidth: 2, color: 'rgb(247,247,247)', startMargin: 60, endMargin: 0 })
}
AlphabetIndexer侧栏索引联动
1、通过onSelect获取索引值,然后通过scrollToIndex(index)滚动到对应位置;
2、list中通过onScrollIndex,判断索引值;
onScrollIndex((firstIndex: number) => {
this.selectedIndex = firstIndex
console.log('**************', firstIndex);
if(firstIndex > 2) {
this.selectedIndex = this.letters.findIndex(i => i === this.fList[firstIndex - 3]['letter'])
}
// 根据列表滚动到的索引值,重新计算对应联系人索引栏的位置this.selectedIndex
})
3、配置索引侧栏
@State letters: string[] = ['→', '※', 'A','B','C','D','E','F','H','I','G','K','L','M','N', 'O','P','Q','R','S','T','U','V','W','X','Y','Z','#']
private listScroller: Scroller = new Scroller();
@State selectedIndex: number = 0;
build() {
AlphabetIndexer({ arrayValue: this.letters, selected: 0 })
.color(Color.Black)
.selectedColor(0xFFFFFF) // 选中项文本颜色
.popupColor(0xFFFAF0) // 弹出框文本颜色
.selectedBackgroundColor('rgb(1,196,194)') // 选中项背景颜色
.popupBackground(0xD2B48C) // 弹出框背景颜色
.usingPopup(true) // 是否显示弹出框
.selectedFont({ size: 13, weight: FontWeight.Bolder }) // 选中项字体样式
.popupFont({ size: 30, weight: FontWeight.Bolder }) // 弹出框内容的字体样式
.itemSize(20) // 每一项的尺寸大小
.alignStyle(IndexerAlign.Left) // 弹出框在索引条左侧弹出
.onSelect((index: number) => {
console.info(this.letters[index] + ' Selected!', index)
this.listScroller.scrollToIndex(index)
})
.onPopupSelect((index: number) => {
console.info('onPopupSelected:' + index)
})
.selected(this.selectedIndex)
}
List实现swipeAction侧滑删除
通过ListItem上面的swipeAction属性进行配置
@Builder itemEnd(index: number) {
// 侧滑后尾端出现的组件
Button({ type: ButtonType.Circle }) {
Image($r('app.media.ic_public_delete_filled'))
.width(20)
.height(20)
.fillColor(Color.Red)
}
.backgroundColor(Color.Transparent)
.padding(12)
.margin({
right: 18
})
.onClick(() => {
// 操作数据/请求后台删除数据;
})
}
build() {
List() {
ForEach(this.list, (item, index) => {
ListItem(){
ContactsRow({imagePath: $rawfile('start1.jpg'),titleString:'老婆'})
.padding({left:15,right:15})
}
.swipeAction({ end: this.itemEnd.bind(this, index) })
})
}
}
图标实现Badge消息提示
通过Badge组件实现,或者通过Stack配合postion定位自定义实现
@Component
struct ContactsRow {
imagePath:string | Resource
titleString:string
count?: number
build(){
Flex({direction:FlexDirection.Row,alignItems:ItemAlign.Center}){
Badge({
count: this.count,
position: BadgePosition.RightTop,
style: { badgeSize: 16, badgeColor: '#FA2A2D' }
}) {
Image(this.imagePath)
.width(41)
.height(41)
.borderRadius(3)
}
Text(this.titleString)
.fontSize(18)
.margin({left:10})
}
.height(55)
}
}
通讯录完整代码
import router from '@ohos.router'
@Entry
@Component
export default struct Contacts {
@State message: string = 'Hello World'
@State fList: object[] = [
{'letter':"A",'data':[
{'name':'A-老张','avtar': $rawfile('icon4.jpg')},
{'name':'A-老张1','avtar':$rawfile('icon4.jpg')},
{'name':'A-老张2','avtar': $rawfile('icon4.jpg')},
{'name':'A-老张3','avtar': $rawfile('icon4.jpg')},
{'name':'A-老张4','avtar': $rawfile('icon4.jpg')},
{'name':'A-老张5','avtar': $rawfile('icon4.jpg')},
]},
{'letter':"L",'data':[
{'name':'老张','avtar':'/images/icon2.png'},
{'name':'老张1','avtar':'/images/icon2.png'},
{'name':'老张2','avtar':'/images/icon2.png'},
{'name':'老张3','avtar':'/images/icon2.png'},
{'name':'老张4','avtar':'/images/icon2.png'},
{'name':'老张5','avtar':'/images/icon2.png'},
]},
{'letter':"W",'data':[
{'name':'王富贵','avtar':'/images/icon3.png'},
{'name':'王富贵1','avtar':'/images/icon3.png'},
{'name':'王富贵2','avtar':'/images/icon3.png'},
{'name':'王富贵3','avtar':'/images/icon3.png'},
{'name':'王富贵4','avtar':'/images/icon3.png'},
{'name':'王富贵5','avtar':'/images/icon3.png'},
{'name':'王富贵6','avtar':'/images/icon3.png'},
]},
{'letter':"Z",'data':[
{'name':'猪头','avtar':'/images/icon1.png'},
{'name':'猪头1','avtar':'/images/icon1.png'},
{'name':'猪头2','avtar':'/images/icon1.png'},
{'name':'猪头3','avtar':'/images/icon1.png'},
{'name':'猪头4','avtar':'/images/icon1.png'},
{'name':'猪头5','avtar':'/images/icon1.png'},
{'name':'猪头6','avtar':'/images/icon1.png'},
{'name':'猪头7','avtar':'/images/icon1.png'},
{'name':'猪头8','avtar':'/images/icon1.png'},
{'name':'猪头9','avtar':'/images/icon1.png'},
{'name':'猪头10','avtar':'/images/icon1.png'},
{'name':'猪头11','avtar':'/images/icon1.png'},
{'name':'猪头12','avtar':'/images/icon1.png'},
]},
]
@State letters: string[] = ['→', '※', 'A','B','C','D','E','F','H','I','G','K','L','M','N', 'O','P','Q','R','S','T','U','V','W','X','Y','Z','#']
@Builder NavigationMenus() { // CustomBuilder类型的菜单栏
Row() {
Image('/images/contact_add.png')
.size({ width: 24, height: 24 })
.margin({ left: 5, top: 16 })
.onClick(()=>{
})
}.justifyContent(FlexAlign.End)
}
@Builder NavigationTitle() {
Row(){
Text("通讯录")
.width('100')
}
.width('100%')
.justifyContent(FlexAlign.Center)
}
@Builder itemHead(text:string) {
Text(text)
.fontSize(14)
.backgroundColor('#E6E6E6')
.width("120%")
.padding({
left: 15,
top: 8,
bottom: 8
})
.fontColor(Color.Gray)
.margin({
left: -15,
})
}
private listScroller: Scroller = new Scroller();
@State selectedIndex: number = 0;
@Builder itemEnd(index: number) {
// 侧滑后尾端出现的组件
Button({ type: ButtonType.Circle }) {
Image($r('app.media.ic_public_delete_filled'))
.width(20)
.height(20)
.fillColor(Color.Red)
}
.backgroundColor(Color.Transparent)
.padding(12)
.margin({
right: 18
})
.onClick(() => {
// 操作数据/请求后台删除数据;
})
}
build() {
Column() {
Navigation(){
Stack({ alignContent: Alignment.End }){
Column(){
List({ scroller: this.listScroller }){
ListItemGroup() {
ListItem(){
ContactsRow({imagePath:'/images/contact_0.png',titleString:'新的朋友'})
}
ListItem(){
ContactsRow({imagePath:'/images/contact_1.png',titleString:'仅聊天的朋友'})
}
ListItem(){
ContactsRow({imagePath:'/images/contact_2.png',titleString:'群聊'})
}
ListItem(){
ContactsRow({imagePath:'/images/contact_3.png',titleString:'标签'})
}
ListItem(){
ContactsRow({imagePath:'/images/contact_4.png',titleString:'公众号'})
}
}
.padding({left:15,right:15})
.divider({ strokeWidth: 2, color: 'rgb(247,247,247)', startMargin: 60, endMargin: 0 })
ListItemGroup() {
ListItem(){
Column() {
Column() {
Text('我的企业及企业联系人').fontSize(14).fontColor(Color.Gray)
}
.backgroundColor('#E6E6E6')
.width('100%')
.height(36)
.padding({
left: 15
})
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Start)
ContactsRow({
imagePath:'/images/contact_5.png',
titleString:'企业微信联系人'
}).padding({left:15,right:15})
}
}
ListItem(){
ContactsRow({
imagePath:'/images/contact_5.png',
titleString:'企业微信通知',
count: 36
}).padding({left:15,right:15})
}
}
.divider({ strokeWidth: 2, color: 'rgb(247,247,247)', startMargin: 60, endMargin: 0 })
ListItemGroup() {
ListItem(){
Column() {
Column() {
Text('星标朋友').fontSize(14).fontColor(Color.Gray)
}
.backgroundColor('#E6E6E6')
.width('100%')
.height(36)
.padding({
left: 15
})
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Start)
}
}
ListItem(){
ContactsRow({imagePath: $rawfile('start1.jpg'),titleString:'老婆'})
.padding({left:15,right:15})
}
.swipeAction({ end: this.itemEnd.bind(this, 0) })
ListItem(){
ContactsRow({imagePath:$rawfile('start2.jpg'),titleString:'大甜'})
.padding({left:15,right:15})
}.swipeAction({ end: this.itemEnd.bind(this, 1) })
ListItem(){
ContactsRow({imagePath:$rawfile('start3.jpg'),titleString:'大甜'})
.padding({left:15,right:15})
}.swipeAction({ end: this.itemEnd.bind(this, 2) })
}
.divider({ strokeWidth: 2, color: 'rgb(247,247,247)', startMargin: 60, endMargin: 0 })
ForEach(this.fList,(item:object,index:number)=>{
ListItemGroup({header:this.itemHead(item['letter'])}){
ForEach(item['data'],(p:object,i:number)=>{
ListItem(){
Flex({direction:FlexDirection.Row,alignItems:ItemAlign.Center}){
Image(p['avtar'])
.width(45)
.height(45)
.borderRadius(5)
Text(p['name'])
.fontSize(18)
.margin({left:10})
}
.height(55)
}
.onClick(()=>{
router.pushUrl({url:'pages/Contact/PersonalPage',params: {
person: p
}})
})
})
}
.divider({ strokeWidth: 2, color: 'rgb(247,247,247)', startMargin: 60, endMargin: 0 })
.padding({left:15,right:15})
.margin({top:10})
})
}
.divider({ strokeWidth: 2, color: 'rgb(247,247,247)', startMargin: 60, endMargin: 0 }) // 每行之间的分界线
.margin({top:17})
.backgroundColor(Color.White)
.height('100%')
.onScrollIndex((firstIndex: number) => {
this.selectedIndex = firstIndex
console.log('**************', firstIndex);
if(firstIndex > 2) {
this.selectedIndex = this.letters.findIndex(i => i === this.fList[firstIndex - 3]['letter'])
}
// 根据列表滚动到的索引值,重新计算对应联系人索引栏的位置this.selectedIndex
})
}
AlphabetIndexer({ arrayValue: this.letters, selected: 0 })
.color(Color.Black)
.selectedColor(0xFFFFFF) // 选中项文本颜色
.popupColor(0xFFFAF0) // 弹出框文本颜色
.selectedBackgroundColor('rgb(1,196,194)') // 选中项背景颜色
.popupBackground(0xD2B48C) // 弹出框背景颜色
.usingPopup(true) // 是否显示弹出框
.selectedFont({ size: 13, weight: FontWeight.Bolder }) // 选中项字体样式
.popupFont({ size: 30, weight: FontWeight.Bolder }) // 弹出框内容的字体样式
.itemSize(20) // 每一项的尺寸大小
.alignStyle(IndexerAlign.Left) // 弹出框在索引条左侧弹出
.onSelect((index: number) => {
console.info(this.letters[index] + ' Selected!', index)
this.listScroller.scrollToIndex(index)
})
.onPopupSelect((index: number) => {
console.info('onPopupSelected:' + index)
})
.selected(this.selectedIndex)
}
.width('100%')
.height('100%')
}
.title(this.NavigationTitle())
.mode(NavigationMode.Stack)
.titleMode(NavigationTitleMode.Mini)
.hideBackButton(true)
.menus(this.NavigationMenus())
.size({ width: '100%', height: '100%' })
.backgroundColor('rgb(237,237,237)')
}
// .bindMenu(this.MyMenu())
.width('100%')
.height('95%')
}
}
@Component
struct ContactsRow {
imagePath:string | Resource
titleString:string
count?: number
build(){
Flex({direction:FlexDirection.Row,alignItems:ItemAlign.Center}){
Badge({
count: this.count,
position: BadgePosition.RightTop,
style: { badgeSize: 16, badgeColor: '#FA2A2D' }
}) {
Image(this.imagePath)
.width(41)
.height(41)
.borderRadius(3)
}
Text(this.titleString)
.fontSize(18)
.margin({left:10})
}
.height(55)
}
}
扩展:LazyForEach,长列表处理
循环渲染适用于短列表,当构建具有大量列表项的长列表时,如果直接采用循环渲染方式,会一次性加载所有的列表元素,会导致页面启动时间过长,影响用户体验。因此,推荐使用数据懒加载(LazyForEach)方式实现按需迭代加载数据,从而提升列表性能。
当使用懒加载方式渲染列表时,为了更好的列表滚动体验,减少列表滑动时出现白块,List组件提供了cachedCount参数用于设置列表项缓存数,只在懒加载LazyForEach中生效。
具体参考文档:https://docs.openharmony.cn/pages/v4.0/zh-cn/application-dev/quick-start/arkts-rendering-control-lazyforeach.md/
List() {
LazyForEach(this.list, item => {
ListItem() {
...
}
})
}.cachedCount(3)
————————————————
原文链接:https://blog.csdn.net/shudaoshanQAQ/article/details/136454612