智能客服
你问我答,随时在线为你解决问题
本文以当前流行的垂类市场中的股票类应用为典型案例,详细介绍“一多”在实际开发中的应用,主要涵盖自选股和个股详情两个典型页面,展示其在直板机、双折叠、三折叠、阔折叠、平板和电脑六种产品形态上的“一次开发,多端部署”。下文将从UX设计、工程管理、移动端页面、电脑端页面四个角度,介绍“一多”股票类应用在开发过程中的最佳实践。
阅读本文前,建议开发者先了解ArkUI(方舟UI框架)和一次开发,多端部署概览相关知识。
下文将从UX设计、工程管理、页面开发三个角度,介绍股票类应用在多设备开发中的最佳实践。
股票类应用的目的是让用户更便捷地办理金融业务。常见类型包括理财、股票、基金等应用及业务场景,核心场景有数据查看和股票交易等。
股票类应用有以下特点:
此类型的应用在多端设备的使用过程中,要保障用户办理金融业务时的基本体验与功能可用性,也需着力优化大屏幕设备上的交互效率。
股票类应用的UX设计可参考金融理财类多设备响应式设计指南的自选股章节,设计参考图如下所示。

建议开发者参考多设备工程部署与发布相关内容,掌握分层架构工程的创建与配置方法后,创建出模板项目工程。然后根据股票类应用的开发需求进行针对性修改,确保工程架构贴合实际业务需求。
在创建“一多”工程时,开发者会面临工程结构目录的划分问题。考虑到复用性和可维护性,本文以股票类应用为例,提供推荐的参考方案。
HarmonyOS的分层架构包括产品定制层、基础特性层和公共能力层,为开发者提供清晰、高效、可扩展的设计架构。详细请参见分层架构设计。
股票类应用根据一多推荐的commons、features、products的"三层工程架构"划分目录。其中,features(基础特性层)按业务功能划分为三个独立模块:股票交易-stockdeal(包含买入弹窗、K线图表、交易详情等组件)、股票详情-stockdetail(包含股票详情页、多窗口入口、信息表格等)以及股票市场-stockmarket(包含市场列表组件)。公共常量、媒体播放工具以及窗口管理工具等需要被不同页面依赖引用的内容,划分为一个commons(公共能力层):基础能力-base。products(产品定制层)包含default和pc两个模块定制了程序标准启动流程和多场景协同场景的入口能力。
- ├──commons
- │ └──base/src/main
- │ └──ets
- │ ├──baseviews // 公共视图组件
- │ ├──models // 公共数据模型
- │ └──utils // 公共工具类
- ├──features
- │ ├──stockdeal/src/main
- │ │ └──ets
- │ │ ├──chartmodels // 图表组件
- │ │ ├──models // 股票交易数据模型
- │ │ ├──viewmodels // 股票交易视图模型
- │ │ └──views // 股票交易视图组件
- │ ├──stockdetail/src/main
- │ │ ├──ets
- │ │ │ ├──models // 股票详情数据模型
- │ │ │ ├──viewmodels // 股票详情视图模型
- │ │ │ ├──pages // 股票详情页
- │ │ │ └──views // 股票详情视图组件
- │ └──stockmarket/src/main
- │ └──ets
- │ ├──models // 股票市场数据模型
- │ ├──viewmodels // 股票市场视图模型
- │ └──views // 股票市场视图组件
- └──products
- ├──default/src/main
- │ ├──ets
- │ │ ├──entryability // 移动端程序入口
- │ │ ├──entrybackupability // 程序备份入口
- │ │ ├──pages // 移动端首页
- │ │ ├──splitScreenAbility // 分屏能力
- │ │ └──splitScreenBackupAbility // 分屏备份能力
- │ └──resources // 应用静态资源目录
- └──pc/src/main
- ├──ets
- │ ├──pages // PC端页面
- │ ├──pcability // PC端程序入口
- │ └──pcbackupability // PC程序备份入口
- └──resources // 应用静态资源目录
窗口模式
多设备股票类界面示例,根据适配的设备,涉及全屏模式、分屏模式、悬浮窗模式、自由窗口模式,可参考窗口模式。其中分屏模式与悬浮窗通常无特殊设计,可通过系统方式进入。应用监听窗口尺寸变化,通过断点刷新UI,将自动适配全屏、分屏、悬浮窗、自由窗口模式下的布局。
使用系统UI组件进入全景多窗,实现一个应用多个窗口并行运行的体验,可参考股票详情页——功能开发:应用多实例-多股比价部分。
窗口方向
通过设置setPreferredOrientation()使应用跟随传感器自动旋转。在类直板机上推荐仅竖屏显示,在双折叠展开态、三折叠G态、平板等大屏幕场景下推荐四方向旋转并受控制中心的旋转开关控制。在股票应用中,通过module.json5配置文件,建议设置为FOLLOW_DESKTOP,具体说明可参考窗口方向。
窗口沉浸式
根据UX设计,实现不同窗口模式(全屏、分屏、悬浮窗)下窗口的沉浸式,可参考窗口沉浸式。全屏、分屏和悬浮窗的沉浸式均可通过setWindowLayoutFullscreen()实现,并进行动态安全区避让。
横向(/纵向)断点 | sm/md | sm/lg | md | lg |
|---|---|---|---|---|
自选股页 |
|
|
|
|
界面开发
横向(/纵向)断点 | sm/md | sm/lg | md | lg、xl |
|---|---|---|---|---|
个股详情页 |
|
|
|
|
界面开发
区域编号 | 简介 | 实现方案 |
|---|---|---|
1 | 交易操作行 | 通过为“去交易”按钮设置layoutWeight布局权重,并使用Blank组件结合断点,实现该按钮的自适应拉伸。 |
2 | 标题 | 居中显示,其他操作两端对齐,空白空间使用Blank实现自适应布局拉伸能力。 |
3 | 行情列表数据 | 通过栅格并结合断点,控制在不同断点下显示不同的列数,列表自适应两列变多列。 |
4 | 中间Tab | 通过List的space属性并结合断点,控制在不同断点下ListItem之间的间距。 |
5 | 曲线图和柱状图 | 使用layoutWeight属性实现拉伸能力。 |
6 | 讨论Tab | 通过List的space属性并结合断点,控制在不同断点下ListItem之间的间距。 |
功能开发:应用多实例-多股比价
应用通过系统提供的MultiWindowEntryInAPP组件,配置需拉起的bundleName与UIAbility(仅限本应用,无法拉起其他应用),单击组件页面进入分屏(双股对比),在分屏状态下,再点击组件进入全景多窗(三股对比)。
下表以Mate X5设备为例,展示应用在分屏及全景多窗模式下的效果。
- | 折叠屏分屏-双股比价 | 折叠屏全景多窗-三股比价 |
|---|---|---|
个股详情页-多股比价 |
|
|
约束条件
MultiWindowEntryInAPP组件依赖全景多窗特性,只有当前设备及屏幕状态支持全景多窗,才支持设置此功能。目前支持全景多窗的设备型态有:
对于不支持的设备型态,该组件不可交互,不响应点击事件。
建议开发者在分屏副窗口左上角设置关闭按钮以直接关闭副窗口,本案例使用返回按钮,是股票比价场景需返回上级页面的特定需求。
开发步骤
应用使用MultiWindowEntryInAPP组件主动分屏或进入全景多窗。具体开发步骤如下:
- import { MultiWindowEntryInAPP } from '@kit.UIDesignKit';
- import { TextModifier } from '@kit.ArkUI';
- import { Want } from '@kit.AbilityKit';
- @Component
- export struct MultiWindowEntryComponent {
- @Link textModifier: TextModifier;
- @Link want: Want;
- @State isShowMultiWindowEntry: boolean = false;
- // ...
-
- build() {
- Row() {
- MultiWindowEntryInAPP({
- want: this.want,
- isShowSubtitle: false,
- multiWindowEntryInAPPStyle: {
- iconOptions: {
- iconSize: 24,
- iconColor: $r('sys.color.font_primary'),
- iconWeight: FontWeight.Normal,
- backgroundColor: $r('sys.color.comp_background_tertiary')
- },
- subtitleOptions: {
- modifier: this.textModifier.fontColor($r('app.color.text_primary_color'))
- }
- }
- })
- .id('MultiWindowEntryInAPP')
- }
- .visibility(this.isShowMultiWindowEntry ? Visibility.Visible : Visibility.None)
- }
- }
- import { MultiWindowEntryComponent } from './MultiWindowEntryComponent';
-
- @Component
- export struct TopTitleBar {
- // ...
- @State textModifier: TextModifier = new TextModifier();
- @State splitScreenWant: Want = {
- // Modify the bundleName, moduleName and abilityName of the current application,
- // and launch the UIAbility within the application.
- bundleName: 'com.example.multiticketclass',
- moduleName: 'multiticketclassdefaultsample',
- abilityName: 'SplitScreenAbility',
- };
- // ...
- build() {
- Row() {
- // ...
- // The area displayed by the icon on the right side
- Row({ space: 16 }) {
- // split screen
- Row() {
- MultiWindowEntryComponent({
- textModifier: this.textModifier,
- want: this.splitScreenWant
- })
- }
- .visibility(this.getMultiWindowVisibility())
- }
- }
- // ...
- }
- }
应用内分屏高阶组件窗口路由方案
建议开发者采用应用级多实例来实现分屏页面的路由管理。以下是页面级多实例与应用级多实例的主要区别,多股比价场景的分屏路由管理采用应用级多实例:
场景 | 路由栈特点 | 是否需要路由改造 | 核心方案 |
|---|---|---|---|
页面级多实例 | 每个UI Ability创建后,基于当前节点改造路由栈 | 需要 | 以当前路由节点生成路由表,开发者手动定义路由方案 |
应用级多实例(推荐) | 每个UI Ability创建独立的相同路由栈 | 不需要 | 每个窗口启动时创建独立路由栈(路由表相同) |
应用内分屏高阶组件窗口路由退栈方案
- let eventData: emitter.EventData = {
- data: {
- 'isStart': 1,
- 'id': 1
- }
- };
- let innerEvent: emitter.InnerEvent = {
- eventId: 1,
- priority: emitter.EventPriority.HIGH
- };
-
- export default class SplitScreenAbility extends UIAbility {
- // ...
-
- onCreate(): void {
- // ...
- emitter.emit(innerEvent, eventData);
- }
-
- onNewWant(): void {
- // ...
- emitter.emit(innerEvent, eventData);
- }
-
- // ...
- }
- @Component
- export struct TopTitleBar {
- // ...
- private innerEvent: emitter.InnerEvent = { eventId: 1 };
- private callBack: Callback<emitter.EventData> = (eventData: emitter.EventData) => {
- Logger.info(`eventData:${eventData}`);
- if (this.pageInfos?.pop) {
- this.pageInfos.pop();
- }
- };
-
- aboutToAppear(): void {
- this.viewModel.loadData();
- if (this.context.abilityInfo.name === 'MultiticketclassdefaultAbility') {
- emitter.on(this.innerEvent, this.callBack);
- }
- }
-
- aboutToDisappear(): void {
- emitter.off(this.innerEvent.eventId, this.callBack);
- }
-
- // ...
- }
- }
应用内分屏高阶组件按钮显隐策略
在应用内分屏高阶组件时,对不支持全景多窗的设备隐藏分屏按钮。方案的主要逻辑为:
- public onWindowSizeChange: (windowSize: window.Size) => void = (windowSize: window.Size) => {
- this.mainWindowInfo.windowSize = windowSize;
- if (this.uiContext) {
- this.mainWindowInfo.widthBp = this.uiContext.getWindowWidthBreakpoint();
- this.mainWindowInfo.heightBp = this.uiContext.getWindowHeightBreakpoint();
- }
- };
- // ...
- updateWindowInfo(): void {
- try {
- // ...
- // Register for window size change monitoring, update window size and width/height breakpoint.
- this.mainWindow.on('windowSizeChange', this.onWindowSizeChange);
- // ...
- AppStorage.setOrCreate(KEY_MAIN_WINDOW_INFO, this.mainWindowInfo);
- } catch (error) {
- let err = error as BusinessError;
- Logger.error(`Failed to update window info. Code: ${err.code}, message: ${err.message}`);
- }
- }
- private timerId: number = -1;
-
- aboutToAppear(): void {
- this.checkMultiWindowEnabled();
- }
-
- aboutToDisappear(): void {
- if (this.timerId !== -1) {
- clearTimeout(this.timerId);
- }
- }
-
- private checkMultiWindowEnabled(): void {
- this.timerId = setTimeout(() => {
- const frameNode = this.getUIContext()?.getFrameNodeById('MultiWindowEntryInAPP');
- const inspectorInfo = JSON.stringify(frameNode?.getInspectorInfo() as InspectorInfo);
- if (inspectorInfo?.search('"enabled":true') && inspectorInfo?.search('"enabled":true') !== -1) {
- this.isShowMultiWindowEntry = true;
- } else {
- this.isShowMultiWindowEntry = false;
- }
- }) as number;
- }
- Row() {
- MultiWindowEntryInAPP({
- want: this.want,
- isShowSubtitle: false,
- multiWindowEntryInAPPStyle: {
- // ...
- }
- })
- .id('MultiWindowEntryInAPP')
- }
- .visibility(this.isShowMultiWindowEntry ? Visibility.Visible : Visibility.None)
本章介绍如何基于现有移动端界面开发方案,实现代码逻辑与布局复用,高效完成股票类应用在电脑设备上的界面开发。
长视频应用在电脑端上支持全屏和自由窗口两种模式,具体实现可参考窗口模式。应用内监听窗口尺寸变化,通过断点刷新UI,即可自动适配全屏和自由窗口模式下的布局。
根据UX设计规范,需要实现全屏和自由窗口下的沉浸式效果,具体实现可参考窗口沉浸式。全屏模式下,通过window.setWindowLayoutFullscreen()实现沉浸式。自由窗口模式下,通过window.setWindowDecorVisible(false)隐藏标题栏,仅保留右上角三键,使页面内容延伸至标题栏区域,实现沉浸式显示效果。
页面布局

区域编号 | 简介 | 实现方案 |
|---|---|---|
1 | 侧边页签 | 同移动端自选股页面对应区域的布局实现方案一致。 |
2 | 指数 | |
3 | 股票列表-工具栏 | |
4 | 股票列表 |
页面布局

区域编号 | 简介 | 实现方案 |
|---|---|---|
1 | 交易操作行 | 同移动端股票详情页对应区域的布局实现方案一致。 |
2 | 标题 | |
3 | 行情列表数据 | |
4 | 中间Tab | |
5 | 曲线图和柱状图 |