下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似微信通讯录的列表与字母导航栏交互效果。

1. 项目结构与基础配置

首先确保你的DevEco Studio项目已配置HarmonyOS 5 SDK,并在config.json中添加必要的权限:

"abilities": [
  {
    "name": "MainAbility",
    "type": "page",
    "launchType": "standard"
  }
],
"reqPermissions": [
  {
    "name": "ohos.permission.READ_CONTACTS"
  }
]

2. 数据模型定义

定义联系人数据结构:

// Contact.ets
export class Contact {
  id: string = "";
  name: string = "";
  avatar: string = "";
  phone: string = "";
  
  // 获取首字母大写
  get firstLetter(): string {
    return this.name.charAt(0).toUpperCase();
  }
}

3. 实现通讯录列表与导航栏

主页面实现

// Index.ets
import { Contact } from './Contact';
import promptAction from '@ohos.promptAction';

@Entry
@Component
struct Index {
  @State contacts: Contact[] = []; // 联系人列表
  @State letters: string[] = []; // 字母导航列表
  @State currentLetter: string = ""; // 当前选中的字母
  @State showLetterTip: boolean = false; // 是否显示字母提示
  
  // 模拟加载联系人数据
  onPageShow() {
    this.loadContacts();
  }

  loadContacts() {
    // 实际项目中应从联系人服务获取数据
    const mockContacts = [
      {id: "1", name: "张三", avatar: "", phone: "13800138000"},
      {id: "2", name: "李四", avatar: "", phone: "13800138001"},
      // 更多联系人...
    ];
    
    this.contacts = mockContacts.sort((a, b) => a.name.localeCompare(b.name));
    this.generateLetters();
  }

  // 生成字母导航列表
  generateLetters() {
    const letterSet = new Set<string>();
    this.contacts.forEach(contact => {
      letterSet.add(contact.firstLetter);
    });
    this.letters = Array.from(letterSet).sort();
  }

  // 滚动到指定字母位置
  scrollToLetter(letter: string) {
    const index = this.contacts.findIndex(item => item.firstLetter === letter);
    if (index >= 0) {
      // 这里需要获取List组件的controller来实现滚动
      // 实际实现见下方完整代码
      this.currentLetter = letter;
      this.showLetterTip = true;
      
      // 1秒后隐藏字母提示
      setTimeout(() => {
        this.showLetterTip = false;
      }, 1000);
    }
  }

  build() {
    Column() {
      // 联系人列表
      List({ space: 0 }) {
        ForEach(this.contacts, (contact: Contact) => {
          ListItem() {
            ContactItem({ contact: contact })
          }
        }, (contact: Contact) => contact.id)
      }
      .width('100%')
      .layoutWeight(1)
      
      // 字母导航栏
      Column() {
        ForEach(this.letters, (letter: string) => {
          Text(letter)
            .fontSize(14)
            .fontColor(this.currentLetter === letter ? '#07C160' : '#666666')
            .margin({ top: 2, bottom: 2 })
            .onClick(() => {
              this.scrollToLetter(letter);
            })
        }, (letter: string) => letter)
      }
      .width(30)
      .margin({ right: 10 })
      .alignItems(HorizontalAlign.End)
      
      // 字母提示框
      if (this.showLetterTip) {
        Text(this.currentLetter)
          .fontSize(32)
          .fontColor(Color.White)
          .backgroundColor('#888888')
          .opacity(0.8)
          .padding(20)
          .borderRadius(10)
          .position({ x: '50%', y: '50%' })
          .align(Alignment.Center)
      }
    }
    .width('100%')
    .height('100%')
  }
}

// 联系人列表项组件
@Component
struct ContactItem {
  @Prop contact: Contact;

  build() {
    Row() {
      Image(this.contact.avatar || 'resources/contact_default.png')
        .width(40)
        .height(40)
        .borderRadius(20)
        .margin({ right: 10 })
      
      Column() {
        Text(this.contact.name)
          .fontSize(16)
        Text(this.contact.phone)
          .fontSize(12)
          .fontColor('#999999')
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)
    }
    .width('100%')
    .padding(10)
    .borderRadius(5)
  }
}

4. 实现列表分组与字母标题

增强列表实现,添加分组标题:

// 修改Index组件的build方法中的List部分
List({ space: 0 }) {
  let lastLetter = "";
  
  ForEach(this.contacts, (contact: Contact) => {
    // 如果首字母变化,添加字母标题
    if (contact.firstLetter !== lastLetter) {
      ListItem() {
        Text(contact.firstLetter)
          .fontSize(18)
          .fontColor('#999999')
          .backgroundColor('#F5F5F5')
          .width('100%')
          .padding(10)
      }
      .sticky(Sticky.Normal)
      lastLetter = contact.firstLetter;
    }
    
    // 联系人项
    ListItem() {
      ContactItem({ contact: contact })
    }
  }, (contact: Contact) => contact.id)
}

5. 实现滑动联动效果

添加列表滚动与字母导航栏的联动:

// 在Index组件中添加
private listController: ListController = new ListController();

// 修改List组件添加控制器和滚动事件
List({ space: 0, controller: this.listController }) {
  // ...列表内容
}
.onScrollIndex((firstVisible: number) => {
  if (firstVisible >= 0 && firstVisible < this.contacts.length) {
    const currentContact = this.contacts[firstVisible];
    this.currentLetter = currentContact.firstLetter;
  }
})

// 修改scrollToLetter方法
scrollToLetter(letter: string) {
  const index = this.contacts.findIndex(item => item.firstLetter === letter);
  if (index >= 0) {
    this.listController.scrollToIndex(index);
    this.currentLetter = letter;
    this.showLetterTip = true;
    
    setTimeout(() => {
      this.showLetterTip = false;
    }, 1000);
  } else {
    promptAction.showToast({ message: `没有${letter}开头的联系人` });
  }
}

6. 添加触摸交互效果

增强字母导航栏的触摸交互:

// 在Index组件中添加
@State isTouching: boolean = false;

// 修改字母导航栏Column
Column() {
  ForEach(this.letters, (letter: string) => {
    Text(letter)
      .fontSize(14)
      .fontColor(this.currentLetter === letter ? '#07C160' : '#666666')
      .margin({ top: 2, bottom: 2 })
      .onTouch((event: TouchEvent) => {
        if (event.type === TouchType.Down || event.type === TouchType.Move) {
          this.isTouching = true;
          this.scrollToLetter(letter);
        } else if (event.type === TouchType.Up) {
          this.isTouching = false;
        }
      })
  }, (letter: string) => letter)
}
.backgroundColor(this.isTouching ? 'rgba(0,0,0,0.1)' : 'rgba(0,0,0,0)')
.borderRadius(15)

7. 完整实现代码

整合所有功能的完整实现:

// Index.ets
import { Contact } from './Contact';
import promptAction from '@ohos.promptAction';

@Entry
@Component
struct Index {
  private listController: ListController = new ListController();
  
  @State contacts: Contact[] = [];
  @State letters: string[] = [];
  @State currentLetter: string = "";
  @State showLetterTip: boolean = false;
  @State isTouching: boolean = false;

  onPageShow() {
    this.loadContacts();
  }

  loadContacts() {
    // 实际项目中应从联系人服务获取数据
    const mockContacts = [
      {id: "1", name: "Alice", avatar: "", phone: "13800138000"},
      {id: "2", name: "Bob", avatar: "", phone: "13800138001"},
      {id: "3", name: "Charlie", avatar: "", phone: "13800138002"},
      // 更多联系人...
    ];
    
    this.contacts = mockContacts.sort((a, b) => a.name.localeCompare(b.name));
    this.generateLetters();
  }

  generateLetters() {
    const letterSet = new Set<string>();
    this.contacts.forEach(contact => {
      letterSet.add(contact.firstLetter);
    });
    this.letters = Array.from(letterSet).sort();
  }

  scrollToLetter(letter: string) {
    const index = this.contacts.findIndex(item => item.firstLetter === letter);
    if (index >= 0) {
      this.listController.scrollToIndex(index);
      this.currentLetter = letter;
      this.showLetterTip = true;
      
      setTimeout(() => {
        this.showLetterTip = false;
      }, 1000);
    } else {
      promptAction.showToast({ message: `没有${letter}开头的联系人` });
    }
  }

  build() {
    Column() {
      // 联系人列表
      List({ space: 0, controller: this.listController }) {
        let lastLetter = "";
        
        ForEach(this.contacts, (contact: Contact) => {
          if (contact.firstLetter !== lastLetter) {
            ListItem() {
              Text(contact.firstLetter)
                .fontSize(18)
                .fontColor('#999999')
                .backgroundColor('#F5F5F5')
                .width('100%')
                .padding(10)
            }
            .sticky(Sticky.Normal)
            lastLetter = contact.firstLetter;
          }
          
          ListItem() {
            ContactItem({ contact: contact })
          }
        }, (contact: Contact) => contact.id)
      }
      .width('100%')
      .layoutWeight(1)
      .onScrollIndex((firstVisible: number) => {
        if (firstVisible >= 0 && firstVisible < this.contacts.length) {
          const currentContact = this.contacts[firstVisible];
          this.currentLetter = currentContact.firstLetter;
        }
      })
      
      // 字母导航栏
      Column() {
        ForEach(this.letters, (letter: string) => {
          Text(letter)
            .fontSize(14)
            .fontColor(this.currentLetter === letter ? '#07C160' : '#666666')
            .margin({ top: 2, bottom: 2 })
            .onTouch((event: TouchEvent) => {
              if (event.type === TouchType.Down || event.type === TouchType.Move) {
                this.isTouching = true;
                this.scrollToLetter(letter);
              } else if (event.type === TouchType.Up) {
                this.isTouching = false;
              }
            })
        }, (letter: string) => letter)
      }
      .width(30)
      .margin({ right: 10 })
      .backgroundColor(this.isTouching ? 'rgba(0,0,0,0.1)' : 'rgba(0,0,0,0)')
      .borderRadius(15)
      .alignItems(HorizontalAlign.End)
      
      // 字母提示框
      if (this.showLetterTip) {
        Text(this.currentLetter)
          .fontSize(32)
          .fontColor(Color.White)
          .backgroundColor('#888888')
          .opacity(0.8)
          .padding(20)
          .borderRadius(10)
          .position({ x: '50%', y: '50%' })
          .align(Alignment.Center)
      }
    }
    .width('100%')
    .height('100%')
  }
}
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐