【鸿蒙实战开发】一多开发实例(即时通讯)

往期推文全新看点

一多即时通讯场景概述

本文从目前流行的垂类市场中,选择即时通讯应用作为典型案例详细介绍 “一多” 在实际开发中的应用。一多即时通讯应用的核心功能为用户交互,主要包含对话聊天、通讯录,社交圈等交互功能。开发者在开发"一多"应用时,经常会遇见多端适配上的问题,本文选择了即时通讯应用的一个常见问题,提供了推荐的解决方案,开发者在"一多"开发中遇见同类问题时可以快速解决。

  • 聊天场景如何进行布局设计

当前系统的产品形态主要有手机、折叠屏、平板和2in1四种,下文的具体实践也将围绕这四种产品形态展开,同时将分别从UX设计、页面开发两个角度给出符合“一多”的参考样例,介绍“一多”即时通讯应用在开发过程中的最佳实践。

  • 架构设计章节介绍一多项目的三层架构,开发者可以去相关链接了解。
  • UX设计章节介绍即时通讯应用的聊天场景的交互逻辑,对于类似的设计要点,开发者可以直接拿来使用。
  • 页面开发章节主要介绍聊天场景的布局能力,介绍如何实现聊天场景,如何适配多设备。

架构设计

HarmonyOS的分层架构主要包括三个层次:产品定制层、基础特性层和公共能力层,为开发者构建了一个清晰、高效、可扩展的设计架构。

UX设计

一多即时通讯应用包含聊天、通讯录,社交圈等交互功能,其中聊天页包含分栏布局设计,因此这里给出聊天页的业务逻辑。

一多即时通讯场景包含以下设计能力:侧边导航、分栏布局。

页面开发

以聊天页为典型页面进行展开,聊天页中包含侧边导航与分栏布局的设计能力,本文着重介绍聊天页如何实现分栏布局。

布局能力

聊天页在不同断点下的UX效果如下,涉及的设计能力是侧边导航,分栏布局。侧边导航参考侧边导航,其中会有详细介绍。

在手机和折叠屏折叠状态设备上,受屏幕大小限制,不能实现分栏布局,需要通过点击或者其他方式跳转到另一个页面,但是在折叠屏展开状态、平板及2ni1产品中屏幕尺寸足够大,可以分栏显示不同的内容,为了使操作更加便捷,在IM对话页中使用分栏布局实现对话功能。

示意图如下:

示意图smmdlg
设计能力点
效果图

从上表中,可以发现在sm断点下呈现的是聊天列表页,当点击某一条聊天记录的时候跳转到聊天详情页面;在md和lg断点下则左侧呈现聊天列表页,右侧呈现聊天详情页。

在一次开发,多端部署场景下,Navigation组件能够自动适配窗口,在窗口较大的场景下自动切换分栏展示效果。因此本文中分栏布局使用Navigation组件实现。

各个设备布局图如下所示:

示意图smmdlg
效果图参照上文表格
布局图

在Navigation组件中,定义了对话列表,出于封装的考虑,并不会在一个页面中去使用NavRouter组件做路由跳转,因此使用NavPathStack栈去做路由处理,所以定义了pageInfo去存储路由栈,当点击对话列表中一条对话信息时,向pageInfo中推送跳转路由。关系大致如下:

// features/home/src/main/ets/pages/Index.ets
Navigation(this.pageInfo) {
  if (this.currentPageIndex === 0) {
    Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center }) {
      ConversationList({
        currentConversationUserName: $currentConversationUserName,
        currentContactUserName: $currentContactUserName
      })
        .flexGrow(1)
        .width('100%')
      HomeTab({ currentPageIndex: $currentPageIndex })
        .width(Adaptive.HomeTabWidth(this.currentBreakpoint))
        .height(Adaptive.HomeTabHeight(this.currentBreakpoint))
        .visibility(this.currentBreakpoint !== 'lg' ? Visibility.Visible : Visibility.None)
    }
    .padding({
      bottom: deviceInfo.deviceType !== '2in1' && this.currentBreakpoint !== 'lg' ? 28 : 0
    })
    .height('100%')

  } else if (this.currentPageIndex === 1) {
    Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center }) {
      ContactsList({
        currentContactUserName: $currentContactUserName,
        currentConversationUserName: $currentConversationUserName,
        currentContactUserIcon: $currentContactUserIcon
      })
        .flexGrow(1)
        .width('100%')
      HomeTab({ currentPageIndex: $currentPageIndex })
        .width(Adaptive.HomeTabWidth(this.currentBreakpoint))
        .height(Adaptive.HomeTabHeight(this.currentBreakpoint))
        .visibility(this.currentBreakpoint !== 'lg' ? Visibility.Visible : Visibility.None)
    }
    .height('100%')
    .padding({
      bottom: deviceInfo.deviceType !== '2in1' && this.currentBreakpoint !== 'lg' ? 28 : 0
    })
  }
}

// features/home/src/main/ets/pages/ConversationList.ets
List() {
  ForEach(ConversationListData, (item: ConversationDataInterface, index: number) => {
    ListItem() {
      ConversationItem(item)
        .onClick(() => {
          if (this.pageInfo.size() > 1) {
            this.pageInfo.pop();
          }
          this.pageInfo.pushPath({ name: 'ConversationDetail' });
          this.currentConversationUserName = item.name;
          this.currentContactUserName = '';
          this.currentIndex = index;
        })
        .backgroundColor(this.currentIndex === index ? $r('app.color.conversation_clicked_bg_color') : Color.White)
    }
    .height(Adaptive.ContactItemHeight(this.currentBreakpoint))

  }, (item: ConversationDataInterface, index: number) => index + JSON.stringify(item))
}

此外需要注意Navigation的模式与宽度在不同设备下是有区分的,具体代码如下:

// features/home/src/main/ets/pages/Index.ets
@Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack();
@Builder
PageMap(name: string) {
  if (name === 'ConversationDetail') {
    ConversationDetail({ currentConversationUserName: this.currentConversationUserName, currentFeatureIndex: 1 });
  } else if (name === 'ConversationDetailNone') {
    ConversationDetailNone();
  } else if (name === 'ContactsDetail') {
    ContactsDetail({
      currentContactUserName: this.currentContactUserName,
      currentContactUserIcon: this.currentContactUserIcon
    });
  } else {
    ConversationDetailNone();
  }
}

build() {
  Column() {
    /**
     * Home and contacts page
     */
    Flex() {
      HomeTab({ currentPageIndex: $currentPageIndex })
        .width(Adaptive.HomeTabWidth(this.currentBreakpoint))
        .backgroundColor($r('app.color.background_color_grey_two'))
        .padding({
          top: 180,
          bottom: 180,
          left: 22
        })
        .visibility(this.currentBreakpoint === 'lg' ? Visibility.Visible : Visibility.None)
      Navigation(this.pageInfo) {
        if (this.currentPageIndex === 0) {
          Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center }) {
            ConversationList({
              currentConversationUserName: $currentConversationUserName,
              currentContactUserName: $currentContactUserName
            })
              .flexGrow(1)
              .width('100%')
            HomeTab({ currentPageIndex: $currentPageIndex })
              .width(Adaptive.HomeTabWidth(this.currentBreakpoint))
              .height(Adaptive.HomeTabHeight(this.currentBreakpoint))
              .visibility(this.currentBreakpoint !== 'lg' ? Visibility.Visible : Visibility.None)
          }
          .padding({
            bottom: deviceInfo.deviceType !== '2in1' && this.currentBreakpoint !== 'lg' ? 28 : 0
          })
          .height('100%')

        } else if (this.currentPageIndex === 1) {
          Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center }) {
            ContactsList({
              currentContactUserName: $currentContactUserName,
              currentConversationUserName: $currentConversationUserName,
              currentContactUserIcon: $currentContactUserIcon
            })
              .flexGrow(1)
              .width('100%')
            HomeTab({ currentPageIndex: $currentPageIndex })
              .width(Adaptive.HomeTabWidth(this.currentBreakpoint))
              .height(Adaptive.HomeTabHeight(this.currentBreakpoint))
              .visibility(this.currentBreakpoint !== 'lg' ? Visibility.Visible : Visibility.None)
          }
          .height('100%')
          .padding({
            bottom: deviceInfo.deviceType !== '2in1' && this.currentBreakpoint !== 'lg' ? 28 : 0
          })
        }
      }
      .hideTitleBar(true)
      .hideToolBar(true)
      .navBarWidth(this.currentBreakpoint === 'lg' ? '44.5%' : '50%')
      .navDestination(this.PageMap)
      .mode(this.currentBreakpoint === 'sm' ? NavigationMode.Stack : NavigationMode.Split)
      .width('100%')
    }
    .visibility(this.currentPageIndex === CurrentPage.HOME || this.currentPageIndex === CurrentPage.CONVERSATION ?
    Visibility.Visible : Visibility.None)

// features/home/src/main/ets/pages/ConversationList.ets
@Component
export struct ConversationList {
  @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm';
  @Link currentConversationUserName: string;
  @Link currentContactUserName: string;
  @State private currentIndex: number = 0;
  @Consume('pageInfo') pageInfo: NavPathStack;

  build() {
    Flex({ direction: FlexDirection.Column }) {
      HomeTopSearch({ title: '消息' })
        .height(Adaptive.ContactItemHeight(this.currentBreakpoint))
      List() {
        ForEach(ConversationListData, (item: ConversationDataInterface, index: number) => {
          ListItem() {
            ConversationItem(item)
              .onClick(() => {
                if (this.pageInfo.size() > 1) {
                  this.pageInfo.pop();
                }
                this.pageInfo.pushPath({ name: 'ConversationDetail' });
                this.currentConversationUserName = item.name;
                this.currentContactUserName = '';
                this.currentIndex = index;
              })
              .backgroundColor(this.currentIndex === index ? $r('app.color.conversation_clicked_bg_color') : Color.White)
          }
          .height(Adaptive.ContactItemHeight(this.currentBreakpoint))

        }, (item: ConversationDataInterface, index: number) => index + JSON.stringify(item))
      }
      .padding({
        bottom: deviceInfo.deviceType !== '2in1' && this.currentBreakpoint === 'lg' ? 28 : 0
      })
      .backgroundColor(Color.White)
      .width('100%')
      .height('100%')
    }

    .height('100%')
    .width('100%')
  }
}

// features/home/src/main/ets/pages/ConversationDetail.ets
@Component
export struct ConversationDetail {
  @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm';
  @Prop currentConversationUserName: string;
  @Prop currentFeatureIndex: number;
  @Consume('pageInfo') pageInfo: NavPathStack;

  build() {
    NavDestination() {
      Flex({ direction: FlexDirection.Column }) {
        ConversationDetailTopSearch({ currentConversationUserName: $currentConversationUserName, })
          .height(Adaptive.ContactItemHeight(this.currentBreakpoint))
        if (this.currentConversationUserName.length === 3) {
          ConversationDetailItem({
            receivedName: $currentConversationUserName,
            isReceived: true,
            content: HomeConstants.CONVERSATION_LIST_APPLET[0],
            isAppletMsg: true,
            currentFeatureIndex: $currentFeatureIndex
          })
          ConversationDetailItem({
            receivedName: $currentConversationUserName,
            isReceived: true,
            content: HomeConstants.CONVERSATION_LIST_APPLET[1],
            currentFeatureIndex: $currentFeatureIndex
          })
          ConversationDetailItem({
            receivedName: $currentConversationUserName,
            isReceived: !true,
            content: HomeConstants.CONVERSATION_LIST_APPLET[2],
            currentFeatureIndex: $currentFeatureIndex
          })
        } else {
          ConversationDetailItem({
            receivedName: $currentConversationUserName,
            isReceived: true,
            content: HomeConstants.CONVERSATION_LIST_DOCUMENT[0],
            isDocumentMsg: true,
            currentFeatureIndex: $currentFeatureIndex
          })
          ConversationDetailItem({
            receivedName: $currentConversationUserName,
            isReceived: true,
            content: HomeConstants.CONVERSATION_LIST_DOCUMENT[1],
            currentFeatureIndex: $currentFeatureIndex
          })
          ConversationDetailItem({
            receivedName: $currentConversationUserName,
            isReceived: !true,
            content: HomeConstants.CONVERSATION_LIST_DOCUMENT[2],
            currentFeatureIndex: $currentFeatureIndex
          })
        }
        Blank()
        ConversationDetailBottom()
      }
      .height('100%')
      .width('100%')
      .backgroundColor($r('app.color.background_color_grey'))
      .padding({
        bottom: deviceInfo.deviceType !== '2in1' ? 28 : 0
      })
    }
    .hideTitleBar(true)
  }
}

交互归一

不同类型的智能设备,系统已经针对不同的交互方式做了适配,实现了 交互归一 ,因此开发者无需额外关注用户不同的交互方式。

本场景包含的交互归一方式如下(以触控屏为例):

1、单指点击对应组件。
2、单指滑动List和Scroll组件。
3、走焦(参考一多开发实例(长视频))。

最后

总是有很多小伙伴反馈说:鸿蒙开发不知道学习哪些技术?不知道需要重点掌握哪些鸿蒙开发知识点? 为了解决大家这些学习烦恼。在这准备了一份很实用的鸿蒙全栈开发学习路线与学习文档给大家用来跟着学习。

针对一些列因素,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植……等)技术知识点。

《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN/733GH/overview

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

在这里插入图片描述

鸿蒙开发面试真题(含参考答案):

在这里插入图片描述

《OpenHarmony源码解析》:

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……
  • 系统架构分析
  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

图片

OpenHarmony 设备开发学习手册:https://gitcode.com/HarmonyOS_MN/733GH/overview

图片

<think>好的,我需要帮助用户寻找与ArkTS网上聊天项目相关的示例代码或开发教程。根据用户提供的引用内容,首先查看各个引用中是否有相关的信息。 引用3提到了在ArkTS中实现socket功能,导入了@arkts/network模块的Socket类,这可能与网络通信相关,适合聊天应用的基础功能。引用4和5也涉及ArkTS的网络通信和UI开发,尤其是引用5中的TCP客户端通讯示例,可能对聊天项目有帮助。此外,引用1和2提到了新闻客户端和聊天实例应用,但具体内容可能不够详细。 接下来,用户需要示例代码和开发教程。根据引用3和5,可以整合网络模块的使用方法,比如创建Socket连接和处理数据传输。同时,引用4提到的ArkUI多端部署和UI框架对于构建聊天界面很重要。需要将这些内容结合起来,分步骤说明如何实现聊天应用的核心功能,包括UI设计、网络通信和数据处理。 还需考虑用户可能需要进一步的技术点,比如消息存储、加密或实时通信优化。因此,在回答中应包含基础实现步骤,并推荐相关学习资源,帮助用户深入理解。同时,确保示例代码符合ArkTS的语法规范,并参考引用中的模块导入方式,比如使用@arkts/network中的Socket类。 最后,生成相关问题,引导用户探索更多细节,如性能优化、消息加密和UI组件使用,确保覆盖用户可能遇到的后续问题。需要检查引用内容是否准确,并正确标注引用标识,例如引用3和5中的代码示例和教程链接。</think>根据您对鸿蒙ArkTS网上聊天项目的需求,结合官方技术文档和开发实战经验,以下提供核心实现思路与代码示例: ### 一、聊天项目核心技术组成 1. **网络通信层**:使用ArkTS Socket API实现TCP/UDP长连接 2. **UI框架层**:ArkUI声明式开发实现消息列表、输入框等组件 3. **数据管理**:使用AppStorage实现跨组件状态管理 4. **安全机制**:集成TLS/SSL进行通信加密[^5] ### 二、核心代码实现 #### 1. 网络连接模块(基于引用3、5) ```typescript // network/chat_socket.ets import { Socket, TLSConnectOptions } from '@arkts/network'; export class ChatClient { private socket?: Socket; // 建立加密连接 async connect(host: string, port: number) { const options: TLSConnectOptions = { address: { host, port }, ALPNProtocols: ['h2'], // HTTP/2协议 certificates: 'cacert.pem' // 证书文件 }; this.socket = new Socket(); await this.socket.connectTLS(options); } // 发送消息 sendMessage(content: string) { const encoder = new TextEncoder(); this.socket?.write(encoder.encode(content)); } // 接收消息 onMessage(callback: (msg: string) => void) { this.socket?.on('data', (data: Uint8Array) => { const decoder = new TextDecoder(); callback(decoder.decode(data)); }); } } ``` #### 2. 聊天界面组件(基于引用4) ```typescript // view/chat_page.ets @Component struct ChatBubble { @Prop message: string; @Prop isSelf: boolean; build() { Column() { Text(this.message) .padding(10) .backgroundColor(this.isSelf ? '#007AFF' : '#E5E5EA') .borderRadius(15) } .alignItems(this.isSelf ? HorizontalAlign.End : HorizontalAlign.Start) } } @Entry @Component struct ChatPage { @State messages: string[] = []; private chatClient = new ChatClient(); aboutToAppear() { this.chatClient.connect('chat.example.com', 443); this.chatClient.onMessage((msg) => { this.messages = [...this.messages, msg]; }); } build() { Column() { List({ space: 10 }) { ForEach(this.messages, (msg, index) => { ListItem() { ChatBubble({ message: msg, isSelf: index % 2 === 0 }) } }) } .layoutWeight(1) TextInput({ placeholder: '输入消息' }) .onSubmit((value) => { this.chatClient.sendMessage(value); this.messages = [...this.messages, value]; }) } .padding(15) } } ``` ### 三、关键开发步骤 1. **环境配置**:确保SDK包含`@arkts/network`模块 2. **证书管理**:将TLS证书放入`resources/rawfile`目录 3. **权限申请**:在`module.json5`添加网络权限: ```json "requestPermissions": [ { "name": "ohos.permission.INTERNET" } ] ``` ### 四、推荐学习路径 1. **基础阶段**:ArkTS语法 → ArkUI组件 → 状态管理 2. **进阶内容**: - 分布式数据同步(跨设备消息同步) - WebSocket实时通信优化 - 消息本地数据库存储(使用RDB模块) 3. **项目实战**:推荐参考[鸿蒙实战课程](https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3)中的即时通信模块[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值