鸿蒙next 高仿QQ好友页面 ,相信你看完也能变成光!

991 阅读4分钟
开源地址 github.com/7-bit-zhang…
鸿蒙next 高仿QQ首页 快来看看我是如何实现的吧!juejin.cn/post/740078…

导读:

这是我在平时抽空写的一个仿QQ的项目,学艺不精希望各位看官大大如果看到有不足的地方指出来让我们共同进步

后续功能等待更新

a4b63498-79da-44c8-8b4c-ea568e5f2a37.png

9b3ce76b-3adb-425b-a156-59bbc61b1a10.png

模块解析

首先开始分析这个页面的结构,最上面的头像+搜索+新朋友/群通知 是不会改变的,好友/分组/群聊等是能切换的,那么我们就能拆分为两个大模块

999129e7-7357-40b5-bc44-1b75453ca223.png

头像的话一看就是使用row进行包装

头像组件
Row({ space: 10 }) {
  Image($rawfile("bit7.jpg"))
    .width(40)
    .height(40)
    .borderRadius(40)
    .objectFit(ImageFit.Auto)
  //.alignRules({ top: {anchor:"1", align: VerticalAlign.Top } })
  Column() {
    Text("联系人")
  }.alignItems(HorizontalAlign.Start).layoutWeight(1)

  Image($rawfile("user_add.svg")).width(25).height(25)
    .fillColor("#282828")
}

接下来就是搜索框,搜索框其实也是使用row,能看到ui中icon和搜索文本是并排的方式排列 如果组件复用的地方多,我们其实可以抽出来封装一下

搜索框
只需要在其他地方调用就好,如果要跨页面的话记得导出组件哦!
@Builder
_buildSearch() {
  Row({ space: 5 }) {
    Image($rawfile("Search_Magnifying_Glass.svg"))
      .width(18)
      .height(18)
      .fillColor(Color.Grey)
    Text("搜索").fontColor(Color.Grey).fontSize(15)
  }
  .width("100%")
  .height(35)
  .backgroundColor("#f5f5f5")
  .borderRadius(6)
  .justifyContent(FlexAlign.Center)
}

接下来就是新朋友/群通知 这个地方很直观能看出来我们该使用row,而且这两个是相同的 如果复制代码写两份的话会很不优雅,这里我们吧它抽成组件能复用

新朋友/群通知

注意我们这里传入了一个onEvent 这个函数是可选参数是绑定的点击事件,当你传入了一个方法函数后点击它就会执行对应的方法

@Builder
_buildRowItem(title: string, onEvent?: (event: ClickEvent) => void) {
  Row() {
    Text(title)
    Blank()
    Image($rawfile("arrow-right.svg"))
      .height(16)
      .fillColor(Color.Grey)
  }
  .width("100%")
  .onClick(onEvent)
  .margin({ top: 15 })
}

剩下的就是好友/分组/群聊列表了,我们继续解析结构,上面这块使用Tabs能实现左右切换滑动当前页面

Tabs
 Tabs({ controller: this.controller, index: this.currentIndex }) {
    TabContent() {
      Text("123")
    }.tabBar(this._buildItemHead("好友", 0))
    TabContent() {
      Text("123")
    }.tabBar(this._buildItemHead("分组", 1))
    TabContent() {
      Text("123")
    }.tabBar(this._buildItemHead("群聊", 2))
    TabContent() {
     Text("123")
    }.tabBar(this._buildItemHead("频道", 3))
     Text("123")
    TabContent() {
      Text("123")
    }.tabBar(this._buildItemHead("设备", 4))
    TabContent() {
      Text("123")
    }.tabBar(this._buildItemHead("通讯录", 5))
  }.layoutWeight(1)
  .onChange(index => this.currentIndex = index)
}
自定义Tabs头部
@Builder
_buildUserList(srcList: Array<User>) {
  List({ space: 15 }) {
    ForEach(srcList, (item: User, index: number) => {
      ListItem() {
        _buildUserItem(item)
      }
    })
  }.layoutWeight(1)
  .scrollBar(BarState.Off)
}

接下来我们需要给User添加一个实力类便于我们进行操作

存储用户的基本信息

export default class User {
  image: ResourceStr;
  name: string;
  stateMsg: string;
  state: UserState;

  constructor(image: ResourceStr, name: string, stateMsg: string, state?: UserState) {
    this.image = image;
    this.name = name;
    this.stateMsg = stateMsg;
    this.state = state ?? UserState.onLine
  }
}
//当前状态
export enum UserState {
  offLine,
  onLine
}
好友列表单个组件

很多地方都会使用这个组件所以我封装成了共用的组件

@Builder
function _buildUserItem(item: User) {
  Row({ space: 15 }) {
    Image(item.image).width(40).height(40).borderRadius(25)
      .colorFilter(
      //如果当前为离线的话给用户头像添加滤镜
        item.state === UserState.offLine ? [
          0.33, 0.33, 0.33, 0, 0, //  # Red output = 0.33 * R + 0.33 * G + 0.33 * B
          0.33, 0.33, 0.33, 0, 0, //# Green output = 0.33 * R + 0.33 * G + 0.33 * B
          0.33, 0.33, 0.33, 0, 0, // # Blue output = 0.33 * R + 0.33 * G + 0.33 * B
          0, 0, 0, 1, 0// # Alpha output = 1 * A (unchanged)
        ] : []
      )
    Column({ space: 5 }) {
      Text(item.name)
      Text(item.stateMsg).fontColor(Color.Grey)
        .fontSize(12)
    }
    .alignItems(HorizontalAlign.Start)
  }
  .width("100%")
  .padding(5)
}
准备实体数据
srcList: Array<User> = [
  new User($rawfile("flutter.png"), "FlutterCandies", "[WiFi在线]"),
  new User($rawfile("harmony.png"), "HarmonyCandies", "[5G在线]"),
  new User($rawfile("img.png"), "造物主动态桌面", "[追剧中。。。]"),
  new User($rawfile("bit7.jpg"), "7_bit", "[手机在线]"),
  new User($rawfile("g.jpg"), "呱呱呱", "[离线]", UserState.offLine),
  new User($rawfile("e.jpg"), "八嘎の君", "[WiFi在线]",),
  new User($rawfile("1.jpg"), "星期一", "[离线]", UserState.offLine),
  new User($rawfile("2.jpg"), "星期二", "[WiFi在线]"),
  new User($rawfile("3.jpg"), "星期三", "[WiFi在线]"),
  new User($rawfile("e.jpg"), "星期四", "[WiFi在线]")
];
好友列表Tabs
///好友列表
@Builder
_buildUserList(srcList: Array<User>) {
  List({ space: 15 }) {
    ForEach(srcList, (item: User, index: number) => {
      ListItem() {
        _buildUserItem(item)
      }
    })
  }.layoutWeight(1)
  .scrollBar(BarState.Off)
}
分组列表

我们使用ListItemGroup来做分组数据,首先使用ListItemGroup需要自定义一个header

@Builder
_buildGroupUserHeader(name: string, count: number, total: number) {
  Row() {
    Text(name)
    Blank()
    Text(`${Math.floor(count)}/${total}`).fontColor(Color.Grey).fontSize(12)
  }.width("100%").padding({ top: 5, bottom: 5 })
  .backgroundColor(Color.White)
}
///分组列表
@Builder
_buildGroupUserList(srcGroupList: Array<GroupUser>) {
  List({ space: 0 }) {
    ForEach(srcGroupList, (item: GroupUser, index: number) => {
      ListItemGroup({
        space: 15,
        header: this._buildGroupUserHeader(item.groupName, this._getOnLineCount(item.groupList),
          item.groupList.length)
      }) {
        ForEach(item.groupList, (userItem: User) => {
          ListItem() {
            _buildUserItem(userItem)
          }
        })
      }.layoutWeight(1)
    })
  }.layoutWeight(1)
  .scrollBar(BarState.Off)
  //ListItemGroup 分组固定头部必须
  .sticky(StickyStyle.Header | StickyStyle.Footer)
}
END
最后上全部代码
View
import ContactsData from '../data/ContactsData'
import GroupUser from '../entity/GroupUser'
import User, { UserState } from '../entity/User'
import MessageListView from './MessageListView'

@Component
export default struct ContactsView {
  private controller: TabsController = new TabsController()
  @State currentIndex: number = 1

  build() {
    Column({ space: 10 }) {
      this._buildAppbar()
      this._buildSearch()
      this._buildRowItem("新朋友", (e) => {
        console.log("点击触发事件,后续加入点击跳转事件")
      })
      this._buildRowItem("群通知")
      this._buildHead();
    }
    .width("100%")
    .height("100%")
    .padding({
      top: 15,
      left: 15,
      right: 15,
      bottom: 5
    })
    .backgroundColor(Color.White)
  }

  @Builder
  _buildAppbar() {
    Row({ space: 10 }) {
      Image($rawfile("bit7.jpg"))
        .width(40)
        .height(40)
        .borderRadius(40)
        .objectFit(ImageFit.Auto)
      //.alignRules({ top: {anchor:"1", align: VerticalAlign.Top } })
      Column() {
        Text("联系人")
      }.alignItems(HorizontalAlign.Start).layoutWeight(1)

      Image($rawfile("user_add.svg")).width(25).height(25)
        .fillColor("#282828")
    }
  }

  @Builder
  _buildSearch() {
    Row({ space: 5 }) {
      Image($rawfile("Search_Magnifying_Glass.svg"))
        .width(18)
        .height(18)
        .fillColor(Color.Grey)
      Text("搜索").fontColor(Color.Grey).fontSize(15)
    }
    .width("100%")
    .height(35)
    .backgroundColor("#f5f5f5")
    .borderRadius(6)
    .justifyContent(FlexAlign.Center)
  }

  @Builder
  _buildRowItem(title: string, onEvent?: (event: ClickEvent) => void) {
    Row() {
      Text(title)
      Blank()
      Image($rawfile("arrow-right.svg"))
        .height(16)
        .fillColor(Color.Grey)
    }
    .width("100%")
    .onClick(onEvent)
    .margin({ top: 15 })
  }

  @Builder
  _buildHead() {
    Tabs({ controller: this.controller, index: this.currentIndex }) {
      TabContent() {
        this._buildUserList(ContactsData.srcList)
      }.tabBar(this._buildItemHead("好友", 0))

      TabContent() {
        this._buildGroupUserList(ContactsData.srcGroupList)
      }.tabBar(this._buildItemHead("分组", 1))

      TabContent() {
        this._buildGroupUserList(ContactsData.srcGroupList)
      }.tabBar(this._buildItemHead("群聊", 2))

      TabContent() {
        this._buildGroupUserList(ContactsData.srcGroupList)
      }.tabBar(this._buildItemHead("频道", 3))

      TabContent() {
        this._buildGroupUserList(ContactsData.srcGroupList)
      }.tabBar(this._buildItemHead("设备", 4))

      TabContent() {
        this._buildGroupUserList(ContactsData.srcGroupList)
      }.tabBar(this._buildItemHead("通讯录", 5))
    }.layoutWeight(1)
    .onChange(index => this.currentIndex = index)
  }

  @Builder
  _buildItemHead(str: string, targetIndex: number) {
    Text(str).fontColor(this.currentIndex === targetIndex ? '#2c80ff' : '#545454')
  }

  ///好友列表
  @Builder
  _buildUserList(srcList: Array<User>) {
    List({ space: 15 }) {
      ForEach(srcList, (item: User, index: number) => {
        ListItem() {
          _buildUserItem(item)
        }
      })
    }.layoutWeight(1)
    .scrollBar(BarState.Off)
  }

  _getOnLineCount(groupList: User[]): number {
    let count: number = 0;
    for (let index = 0; index < groupList.length; index++) {
      if (groupList[index].state == UserState.onLine) {
        count++
      }
    }
    return count;
  }

  ///分组列表
  @Builder
  _buildGroupUserList(srcGroupList: Array<GroupUser>) {
    List({ space: 0 }) {
      ForEach(srcGroupList, (item: GroupUser, index: number) => {
        ListItemGroup({
          space: 15,
          header: this._buildGroupUserHeader(item.groupName, this._getOnLineCount(item.groupList),
            item.groupList.length)
        }) {
          ForEach(item.groupList, (userItem: User) => {
            ListItem() {
              _buildUserItem(userItem)
            }
          })
        }.layoutWeight(1)
      })
    }.layoutWeight(1)
    .scrollBar(BarState.Off)
    //ListItemGroup 分组固定头部必须
    .sticky(StickyStyle.Header | StickyStyle.Footer)
  }

  @Builder
  _buildGroupUserHeader(name: string, count: number, total: number) {
    Row() {
      Text(name)
      Blank()
      Text(`${Math.floor(count)}/${total}`).fontColor(Color.Grey).fontSize(12)
    }.width("100%").padding({ top: 5, bottom: 5 })
    .backgroundColor(Color.White)
  }
}

@Builder
function _buildUserItem(item: User) {
  Row({ space: 15 }) {
    Image(item.image).width(40).height(40).borderRadius(25)
      .colorFilter(
        item.state === UserState.offLine ? [
          0.33, 0.33, 0.33, 0, 0, //  # Red output = 0.33 * R + 0.33 * G + 0.33 * B
          0.33, 0.33, 0.33, 0, 0, //# Green output = 0.33 * R + 0.33 * G + 0.33 * B
          0.33, 0.33, 0.33, 0, 0, // # Blue output = 0.33 * R + 0.33 * G + 0.33 * B
          0, 0, 0, 1, 0// # Alpha output = 1 * A (unchanged)
        ] : []
      )
    Column({ space: 5 }) {
      Text(item.name)
      Text(item.stateMsg).fontColor(Color.Grey)
        .fontSize(12)
    }
    .alignItems(HorizontalAlign.Start)
  }
  .width("100%")
  .padding(5)
}
Data
import GroupUser from '../entity/GroupUser';
import User, { UserState } from '../entity/User';

export default class ContactsData {
  static srcList: Array<User> = [
    new User($rawfile("flutter.png"), "FlutterCandies", "[WiFi在线]"),
    new User($rawfile("harmony.png"), "HarmonyCandies", "[5G在线]"),
    new User($rawfile("img.png"), "造物主动态桌面", "[追剧中。。。]"),
    new User($rawfile("bit7.jpg"), "7_bit", "[手机在线]"),
    new User($rawfile("g.jpg"), "呱呱呱", "[离线]", UserState.offLine),
    new User($rawfile("e.jpg"), "八嘎の君", "[WiFi在线]",),
    new User($rawfile("1.jpg"), "星期一", "[离线]", UserState.offLine),
    new User($rawfile("2.jpg"), "星期二", "[WiFi在线]"),
    new User($rawfile("3.jpg"), "星期三", "[WiFi在线]"),
    new User($rawfile("e.jpg"), "星期四", "[WiFi在线]")
  ];
  static srcGroupList: Array<GroupUser> = [
    new GroupUser("特别关心")
    , new GroupUser("我的群聊",
    [new User($rawfile("flutter.png"), "FlutterCandies", "[WiFi在线]"),
      new User($rawfile("harmony.png"), "HarmonyCandies", "[5G在线]"),
      new User($rawfile("img.png"), "造物主动态桌面", "[追剧中。。。]"),])
    , new GroupUser("关注好友", [new User($rawfile("bit7.jpg"), "7_bit", "[手机在线]"),])
    , new GroupUser("同学分组", [
    new User($rawfile("g.jpg"), "呱呱呱", "[离线]", UserState.offLine),
    new User($rawfile("e.jpg"), "八嘎の君", "[离线]", UserState.offLine),
    new User($rawfile("e.jpg"), "八嘎の君", "[WiFi在线]"),
    new User($rawfile("1.jpg"), "星期一", "[离线]", UserState.offLine),
    new User($rawfile("2.jpg"), "星期二", "[WiFi在线]"),
    new User($rawfile("3.jpg"), "星期三", "[WiFi在线]"),
    new User($rawfile("4.jpg"), "星期四", "[WiFi在线]")
  ])
  ];
}