已合并
master - docs(wms-layout): add AdjustLayout sample #9388
master - docs(wms-layout): add AdjustLayout sample #9388
已合并
yyuehao创建于 5月15日
共 44 个文件变更+1636-1
@@ -53,4 +53,4 @@ export default class NocalculationsupportAbility extends UIAbility {
53 // Ability has back to background53 // Ability has back to background
54 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');54 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
55 }55 }
56-}56+}
@@ -0,0 +1,12 @@
1+/node_modules
2+/oh_modules
3+/local.properties
4+/.idea
5+**/build
6+/.hvigor
7+.cxx
8+/.clangd
9+/.clang-format
10+/.clang-tidy
11+**/.test
12+/.appanalyzer
@@ -0,0 +1,25 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+{
17+ "app": {
18+ "bundleName": "com.samples.adjustlayout",
19+ "vendor": "example",
20+ "versionCode": 1000000,
21+ "versionName": "1.0.0",
22+ "icon": "$media:layered_image",
23+ "label": "$string:app_name"
24+ }
25+}
@@ -0,0 +1,8 @@
1+{
2+ "string": [
3+ {
4+ "name": "app_name",
5+ "value": "AdjustlLayout"
6+ }
7+ ]
8+}
@@ -0,0 +1,7 @@
1+{
2+ "layered-image":
3+ {
4+ "background" : "$media:background",
5+ "foreground" : "$media:foreground"
6+ }
7+}
@@ -0,0 +1,93 @@
1+# AdjustLayout简介
2+ 
3+### 介绍
4+ 
5+在多窗口场景下,开发者需要对主窗口和子窗口进行灵活的布局调整。本示例演示了如何对主窗口设置宽高比例限制,以及如何创建并管理子窗口,包括子窗口的显示/隐藏、拖拽大小、拖拽位置、设置窗口尺寸限制等功能。通过本示例,开发者可以掌握窗口布局调整的核心API使用方法。
6+ 
7+### 效果预览
8+ 
9+| 主窗口 | 子窗口 | 交互效果 |
10+|----------------------------------------|----------------------------------------|----------------------------------------|
11+| ![image](screenshots/screenshot_1.jpg) | ![image](screenshots/screenshot_2.jpg) | ![image](screenshots/screenshot_3.gif) |
12+ 
13+### 使用说明
14+ 
15+1. 启动应用后,会显示主窗口,同时创建子窗口
16+2. 点击"开启主窗比例限制"按钮,主窗口将保持固定的宽高比例(1.5)
17+3. 点击"显示子窗/隐藏子窗"按钮,可以控制子窗口的显示和隐藏
18+4. 点击"开启子窗拖拽大小/关闭子窗拖拽大小"按钮,可以控制子窗口是否允许拖拽调整大小
19+5. 点击"开启子窗拖拽位置/关闭子窗拖拽位置"按钮,可以控制子窗口是否允许拖拽移动位置
20+6. 在输入框中设置子窗口的 minWidth、maxWidth、minHeight、maxHeight,点击"设置子窗口 windowLimits"按钮应用尺寸限制
21+ 
22+### 工程目录
23+ 
24+```
25+entry/src/main/ets/
26+|---main
27+| |---ets
28+| | |---entryability
29+| | | |---EntryAbility.ets // 创建主窗口和子窗口,设置窗口属性
30+| | |---entrybackupability
31+| | |---pages
32+| | | |---Index.ets // 主窗口页面,提供窗口控制功能
33+| | | |---SubWindowPage.ets // 子窗口页面,显示窗口状态信息
34+| |---resources
35+| |---module.json5
36+|---ohosTest
37+| |---ets
38+| | |---test
39+| | | |---Ability.test.ets // 自动化测试代码
40+| | | |---List.test.ets // 测试套件入口
41+```
42+ 
43+### 具体实现
44+ 
45+窗口布局调整的方法在EntryAbility和Index中实现,源码参考:[EntryAbility.ets](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkUISample/ArkUIWindowSamples/AdjustLayout/entry/src/main/ets/entryability/EntryAbility.ets)
46+ 
47+**EntryAbility.ets**
48+- 使用 `getMainWindowSync` 获取应用主窗口
49+- 使用 `setWindowLimits` 设置主窗口的最小/最大宽高限制
50+- 使用 `windowStage.createSubWindow` 创建子窗口
51+- 使用 `resizeAsync` 和 `moveWindowToAsync` 设置子窗口初始尺寸和位置
52+- 使用 `enableDrag` 设置子窗口是否可拖拽
53+- 注册 `windowRectChange` 监听窗口尺寸变化
54+ 
55+**Index.ets**
56+- 使用 `setAspectRatio` 设置主窗口的宽高比例限制
57+- 使用 `resetAspectRatio` 取消宽高比例限制
58+- 使用 `showWindow` 和 `minimize` 控制子窗口显示/隐藏
59+- 使用 `setWindowLimits` 设置子窗口的尺寸限制
60+- 使用 `startMoving` 启动子窗口的拖拽移动
61+ 
62+**SubWindowPage.ets**
63+- 显示当前子窗口的尺寸信息
64+- 根据窗口尺寸动态切换布局样式(Compact Layout)
65+- 处理子窗口标题栏的拖拽移动事件
66+ 
67+### 相关权限
68+ 
69+不涉及
70+ 
71+### 依赖
72+ 
73+不涉及
74+ 
75+### 约束与限制
76+ 
77+1.本示例仅支持标准系统上运行, 支持设备:华为手机、平板、2in1。
78+ 
79+2.本示例为Stage模型,支持API Version 23及以上版本SDK。
80+ 
81+3.本示例需要使用DevEco Studio 6.0.0 Release及以上版本才可编译运行。
82+ 
83+### 下载
84+ 
85+如需单独下载本工程,执行如下命令:
86+ 
87+```
88+git init
89+git config core.sparsecheckout true
90+echo code/DocsSample/ArkUISample/ArkUIWindowSamples/AdjustLayout > .git/info/sparse-checkout
91+git remote add origin https://gitcode.com/openharmony/applications_app_samples.git
92+git pull origin master
93+```
@@ -0,0 +1,43 @@
1+{
haoxiaohui
haoxiaohuihaoxiaohui5月27日

json5文件补充版权头

likedislike
2+ "app": {
3+ "signingConfigs": [],
4+ "products": [
5+ {
6+ "name": "default",
7+ "signingConfig": "default",
8+ "targetSdkVersion": 20,
9+ "compileSdkVersion": 20,
10+ "compatibleSdkVersion": 20,
11+ "runtimeOS": "OpenHarmony",
12+ "buildOption": {
13+ "strictMode": {
14+ "caseSensitiveCheck": true,
15+ "useNormalizedOHMUrl": true
16+ }
17+ }
18+ }
19+ ],
20+ "buildModeSet": [
21+ {
22+ "name": "debug"
23+ },
24+ {
25+ "name": "release"
26+ }
27+ ]
28+ },
29+ "modules": [
30+ {
31+ "name": "entry",
32+ "srcPath": "./entry",
33+ "targets": [
34+ {
35+ "name": "default",
36+ "applyToProducts": [
37+ "default"
38+ ]
39+ }
40+ ]
41+ }
42+ ]
43+}
@@ -0,0 +1,47 @@
1+/*
2+ * Copyright (c) 2025 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+
16+{
17+ "files": [
18+ "**/*.ets"
19+ ],
20+ "ignore": [
21+ "**/src/ohosTest/**/*",
22+ "**/src/test/**/*",
23+ "**/src/mock/**/*",
24+ "**/node_modules/**/*",
25+ "**/oh_modules/**/*",
26+ "**/build/**/*",
27+ "**/.preview/**/*"
28+ ],
29+ "ruleSet": [
30+ "plugin:@performance/recommended",
31+ "plugin:@typescript-eslint/recommended"
32+ ],
33+ "rules": {
34+ "@security/no-unsafe-aes": "error",
35+ "@security/no-unsafe-hash": "error",
36+ "@security/no-unsafe-mac": "warn",
37+ "@security/no-unsafe-dh": "error",
38+ "@security/no-unsafe-dsa": "error",
39+ "@security/no-unsafe-ecdsa": "error",
40+ "@security/no-unsafe-rsa-encrypt": "error",
41+ "@security/no-unsafe-rsa-sign": "error",
42+ "@security/no-unsafe-rsa-key": "error",
43+ "@security/no-unsafe-dsa-key": "error",
44+ "@security/no-unsafe-dh-key": "error",
45+ "@security/no-unsafe-3des": "error"
46+ }
47+}
@@ -0,0 +1,6 @@
1+/node_modules
2+/oh_modules
3+/.preview
4+/build
5+/.cxx
6+/.test
@@ -0,0 +1,48 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+
16+{
17+ "apiType": "stageMode",
18+ "buildOption": {
19+ "resOptions": {
20+ "copyCodeResource": {
21+ "enable": false
22+ }
23+ }
24+ },
25+ "buildOptionSet": [
26+ {
27+ "name": "release",
28+ "arkOptions": {
29+ "obfuscation": {
30+ "ruleOptions": {
31+ "enable": false,
32+ "files": [
33+ "./obfuscation-rules.txt"
34+ ]
35+ }
36+ }
37+ }
38+ },
39+ ],
40+ "targets": [
41+ {
42+ "name": "default"
43+ },
44+ {
45+ "name": "ohosTest",
46+ }
47+ ]
48+}
@@ -0,0 +1,21 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+import { hapTasks } from '@ohos/hvigor-ohos-plugin';
17+ 
18+export default {
19+ system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
20+ plugins: [] /* Custom plugin to extend the functionality of Hvigor. */
21+}
@@ -0,0 +1,38 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+# Define project specific obfuscation rules here.
17+# You can include the obfuscation configuration files in the current module's build-profile.json5.
18+#
19+# For more details, see
20+# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/source-obfuscation
21+ 
22+# Obfuscation options:
23+# -disable-obfuscation: disable all obfuscations
24+# -enable-property-obfuscation: obfuscate the property names
25+# -enable-toplevel-obfuscation: obfuscate the names in the global scope
26+# -compact: remove unnecessary blank spaces and all line feeds
27+# -remove-log: remove all console.* statements
28+# -print-namecache: print the name cache that contains the mapping from the old names to new names
29+# -apply-namecache: reuse the given cache file
30+ 
31+# Keep options:
32+# -keep-property-name: specifies property names that you want to keep
33+# -keep-global-name: specifies names that you want to keep in the global scope
34+ 
35+-enable-property-obfuscation
36+-enable-toplevel-obfuscation
37+-enable-filename-obfuscation
38+-enable-export-obfuscation
@@ -0,0 +1,25 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+{
17+ "name": "entry",
18+ "version": "1.0.0",
19+ "description": "Please describe the basic information.",
20+ "main": "",
21+ "author": "",
22+ "license": "",
23+ "dependencies": {}
24+}
25+ 
@@ -0,0 +1,207 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+
16+// [Start sub_move_resize]
17+// EntryAbility.ets
18+import { UIAbility } from '@kit.AbilityKit';
19+import { window, display } from '@kit.ArkUI';
20+import { BusinessError } from '@kit.BasicServicesKit';
21+import hilog from '@ohos.hilog';
22+ 
23+const DOMAIN = 0x0000;
24+const TAG = 'Sample_AdjustLayout';
25+ 
26+// [StartExclude sub_move_resize]
27+export default class MainAbility extends UIAbility {
28+ private mainWindow?: window.Window;
29+ private subWindow?: window.Window;
30+ 
31+ onWindowStageCreate(windowStage: window.WindowStage): void {
32+ hilog.info(DOMAIN, TAG, 'onWindowStageCreate start');
33+ 
34+ this.mainWindow = windowStage.getMainWindowSync();
35+ AppStorage.setOrCreate<window.Window>('MAIN_WINDOW', this.mainWindow);
36+ 
37+ // 初始化参数
38+ this.initAppStorageState();
39+ 
40+ // 加载主窗口页面内容
41+ windowStage.loadContent('pages/Index', (err: BusinessError) => {
42+ if (err && err.code) {
43+ hilog.error(DOMAIN, TAG, `load Index failed: ${err.code}, ${err.message}`);
44+ return;
45+ }
46+ hilog.info(DOMAIN, TAG, 'load Index callback success');
47+ });
48+ 
49+ // 步骤2:注册主窗口尺寸变化监听
50+ this.mainWindow.on('windowRectChange', (data: window.RectChangeOptions) => {
51+ this.onWindowRectChange(data);
52+ });
53+ 
54+ // 设置主窗口尺寸限制
55+ this.setMainWindowLimitsSafe();
56+ 
57+ // 步骤4:创建子窗口,为子窗口指定初始位置大小
58+ this.createSubWindow(windowStage);
59+ }
60+ 
61+ /**
62+ 初始化应用存储状态
63+ 设置窗口相关的默认配置参数
64+ */
65+ private initAppStorageState(): void {
66+ AppStorage.setOrCreate('WINDOW_ASPECT_RATIO_ENABLED', false);
67+ AppStorage.setOrCreate('SUB_WINDOW_VISIBILITY_ENABLED', false);
68+ AppStorage.setOrCreate('SUB_WINDOW_RESIZE_ENABLED', true);
69+ AppStorage.setOrCreate('SUB_WINDOW_MOVE_ENABLED', true);
70+ AppStorage.setOrCreate('WINDOW_SIZE', {
71+ width: 600,
72+ height: 300
73+ });
74+ }
75+ /**
76+ 注册主窗口尺寸变化监听
77+ 监听主窗口rect变化事件并记录日志
78+ */
79+ private onWindowRectChange(options: window.RectChangeOptions): void {
80+ // ...
81+ hilog.info(DOMAIN, TAG, `main rect change: ${JSON.stringify(options)}`);
82+ }
83+ 
84+ /**
85+ 设置主窗口安全尺寸限制
86+ 根据屏幕尺寸设置主窗口的最小和最大宽高限制
87+ */
88+ private setMainWindowLimitsSafe(): void {
89+ if (!this.mainWindow) {
90+ return;
91+ }
92+ let screenWidth = 0;
93+ let screenHeight = 0;
94+ 
95+ try {
96+ const defaultDisplay = display.getDefaultDisplaySync();
97+ screenWidth = defaultDisplay.width;
98+ screenHeight = defaultDisplay.height;
99+ } catch (err) {
100+ hilog.error(DOMAIN, TAG, `get display failed: ${JSON.stringify(err)}`);
101+ return;
102+ }
103+ let windowLimits: window.WindowLimits = {
104+ minWidth: Math.floor(screenWidth * 0.5),
105+ minHeight: Math.floor(screenHeight * 0.5),
106+ maxWidth: Math.floor(screenWidth * 1.2),
107+ maxHeight: Math.floor(screenHeight * 1.2)
108+ }
109+ let promise = this.mainWindow.setWindowLimits(windowLimits);
110+ promise.then(() => {
111+ hilog.info(DOMAIN, TAG, 'set main window limits success');
112+ }).catch((err: Error) => {
113+ const businessError = err as BusinessError;
114+ hilog.error(DOMAIN, TAG, `set main window limits failed: ${JSON.stringify(businessError)}`);
115+ });
116+ }
117+ 
118+// [EndExclude sub_move_resize]
119+ /**
120+ * 创建子窗口并调整位置大小
121+ */
122+ private createSubWindow(windowStage: window.WindowStage): void {
123+ if (!this.mainWindow) {
124+ return;
125+ }
126+ const mainRect = this.mainWindow.getWindowProperties().windowRect;
127+ const subWidth = 600;
128+ const subHeight = 300;
129+ const margin = 24;
130+ 
131+ // 计算子窗口位置:放在主窗口右下角
132+ const subX = Math.max(
133+ mainRect.left + margin,
134+ mainRect.left + mainRect.width - subWidth - margin
135+ );
136+ 
137+ const subY = Math.max(
138+ mainRect.top + margin,
139+ mainRect.top + mainRect.height - subHeight - margin
140+ );
141+ 
142+ // 创建子窗口
143+ windowStage.createSubWindow('DemoSubWindow').then((subWindow: window.Window) => {
144+ hilog.info(DOMAIN, TAG, 'createSubWindow success');
145+ 
146+ this.subWindow = subWindow;
147+ AppStorage.setOrCreate<window.Window>('SUB_WINDOW', subWindow);
148+ AppStorage.setOrCreate<window.Size>('WINDOW_SIZE', {
149+ width: subWidth,
150+ height: subHeight
151+ });
152+ 
153+ // 注册子窗口尺寸变化监听
154+ this.registerSubWindowRectChange(subWindow);
155+ // 设置子窗口尺寸
156+ this.subWindow!.resizeAsync(subWidth, subHeight);
157+ // 移动子窗口位置
158+ this.subWindow!.moveWindowToAsync(subX, subY);
159+ // 子窗口设置为默认可拖拽
160+ this.subWindow!.enableDrag(true);
161+ // 加载子窗口内容页面
162+ this.loadSubWindowContent(this.subWindow!);
163+ }).catch((err: Error) => {
164+ const businessError = err as BusinessError;
165+ hilog.error(DOMAIN, TAG, `createSubWindow failed: ${JSON.stringify(businessError)}`);
166+ });
167+ }
168+// [End sub_move_resize]
169+ 
170+ /**
171+ 注册子窗口尺寸变化监听
172+ 监听子窗口rect变化事件并更新应用存储中的窗口尺寸信息
173+ */
174+ private registerSubWindowRectChange(subWindow: window.Window): void {
175+ subWindow.on('windowRectChange', (data: window.RectChangeOptions) => {
176+ const rect = data.rect;
177+ AppStorage.setOrCreate('WINDOW_SIZE', {
178+ width: rect.width,
179+ height: rect.height
180+ });
181+ hilog.info(DOMAIN, TAG, `sub rect change: ${JSON.stringify(data)}`);
182+ });
183+ }
184+ 
185+ /**
186+ 加载子窗口内容页面
187+ 设置子窗口的UI内容为SubWindowPage页面
188+ */
189+ private loadSubWindowContent(subWindow: window.Window): void {
190+ subWindow.setUIContent('pages/SubWindowPage').then(() => {
191+ console.info('Succeeded in loading the content.');
192+ }).catch((err: Error) => {
193+ const businessError = err as BusinessError;
194+ console.error(`Failed to load the content. Cause code: ${businessError.code}, message: ${businessError.message}`);
195+ });
196+ }
197+ onWindowStageDestroy(): void {
198+ try {
199+ this.mainWindow?.off('windowRectChange');
200+ this.subWindow?.off('windowRectChange');
201+ this.subWindow?.destroyWindow();
202+ } catch (err) {
203+ const businessError = err as BusinessError;
204+ hilog.error(DOMAIN, TAG, `destroy window failed: ${JSON.stringify(businessError)}`);
205+ }
206+ }
207+}
@@ -0,0 +1,31 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+import { hilog } from '@kit.PerformanceAnalysisKit';
17+import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit';
18+ 
19+const DOMAIN = 0x0000;
20+ 
21+export default class EntryBackupAbility extends BackupExtensionAbility {
22+ async onBackup() {
23+ hilog.info(DOMAIN, 'Sample_AdjustLayout', 'onBackup ok');
24+ await Promise.resolve();
25+ }
26+ 
27+ async onRestore(bundleVersion: BundleVersion) {
28+ hilog.info(DOMAIN, 'Sample_AdjustLayout', 'onRestore ok %{public}s', JSON.stringify(bundleVersion));
29+ await Promise.resolve();
30+ }
31+}
@@ -0,0 +1,344 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+// [Start aspectRatio]
17+// [Start enableDrag]
18+// Index.ets
19+import { window } from '@kit.ArkUI';
20+import { BusinessError } from '@kit.BasicServicesKit';
21+import hilog from '@ohos.hilog';
22+ 
23+const DOMAIN = 0x0000;
24+const TAG = 'IndexPage';
25+ 
26+// [StartExclude aspectRatio]
27+// [StartExclude enableDrag]
28+@Entry
29+@Component
30+struct Index {
31+ @StorageLink('WINDOW_ASPECT_RATIO_ENABLED') aspectRatioEnabled: boolean = false;
32+ @StorageLink('SUB_WINDOW_VISIBILITY_ENABLED')showSubWindowEnabled: boolean = false;
33+ @StorageLink('SUB_WINDOW_RESIZE_ENABLED') resizeEnabled: boolean = true;
34+ @StorageLink('SUB_WINDOW_MOVE_ENABLED') moveEnabled: boolean = true;
35+ 
36+ @State minWidthText: string = '200';
37+ @State maxWidthText: string = '1000';
38+ @State minHeightText: string = '100';
39+ @State maxHeightText: string = '500';
40+ @State resultText: string = '等待操作';
41+ 
42+ private getMainWindow(): window.Window | undefined {
43+ return AppStorage.get<window.Window>('MAIN_WINDOW');
44+ }
45+ 
46+ private getSubWindow(): window.Window | undefined {
47+ return AppStorage.get<window.Window>('SUB_WINDOW');
48+ }
49+ 
50+ /**
51+ 
52+ 控制子窗口显示隐藏
53+ */
54+ private setSubWindowVisibilityEnabled(enabled: boolean): void {
55+ const subWindow = this.getSubWindow();
56+ if (!subWindow) {
57+ this.resultText = '子窗口未创建';
58+ return;
59+ }
60+ if (enabled) {
61+ subWindow.showWindow().then(() => {
62+ AppStorage.setOrCreate('SUB_WINDOW_VISIBILITY_ENABLED', enabled);
63+ this.resultText = '已显示子窗口';
64+ console.info('drr:Succeeded in show subwindow.');
65+ }).catch((err: Error) => {
66+ const businessError = err as BusinessError;
67+ this.resultText = `显示子窗口失败: ${JSON.stringify(businessError)}`;
68+ hilog.error(DOMAIN, TAG, `show subwindow failed: ${JSON.stringify(businessError)}`);
69+ });
70+ } else {
71+ subWindow.minimize().then(() => {
72+ AppStorage.setOrCreate('SUB_WINDOW_VISIBILITY_ENABLED', enabled);
73+ this.resultText = '已隐藏子窗口';
74+ console.info('drr:Succeeded in hide subwindow.');
75+ }).catch((err: Error) => {
76+ const businessError = err as BusinessError;
77+ this.resultText = `隐藏子窗口失败: ${JSON.stringify(businessError)}`;
78+ hilog.error(DOMAIN, TAG, `hide subwindow failed: ${JSON.stringify(businessError)}`);
79+ });
80+ }
81+ }
82+ 
83+// [EndExclude enableDrag]
84+ /**
85+ 通过enableDrag控制子窗口的拖拽缩放功能
86+ */
87+ private setSubWindowResizeEnabled(enabled: boolean): void {
88+ const subWindow = this.getSubWindow();
89+ if (!subWindow) {
90+ this.resultText = '子窗口未创建';
91+ return;
92+ }
93+ 
94+ subWindow.enableDrag(enabled)
95+ .then(() => {
96+ AppStorage.setOrCreate<boolean>('SUB_WINDOW_RESIZE_ENABLED', enabled);
97+ this.resultText = enabled ? '已开启子窗拖拽大小' : '已关闭子窗拖拽大小';
98+ })
99+ .catch((err: Error) => {
100+ const businessError = err as BusinessError;
101+ this.resultText = `设置拖拽大小失败: ${JSON.stringify(businessError)}`;
102+ hilog.error(DOMAIN, TAG, `set resize drag failed: ${JSON.stringify(businessError)}`);
103+ });
104+ }
105+// [End enableDrag]
106+ 
107+ /**
108+ 通过startMoving控制子窗口的拖拽移动功能
109+ */
110+ private setSubWindowMoveEnabled(enabled: boolean): void {
111+ AppStorage.setOrCreate('SUB_WINDOW_MOVE_ENABLED', enabled);
112+ this.resultText = enabled ? '已开启子窗拖拽位置' : '已关闭子窗拖拽位置';
113+ }
114+ 
115+// [EndExclude aspectRatio]
116+ /**
117+ 步骤2:为主窗口设置指定宽高比例
118+ */
119+ private setWindowAspectRatioEnabled(enabled: boolean): void {
120+ const mainWindow = this.getMainWindow();
121+ if (!mainWindow) {
122+ this.resultText = '找不到主窗口';
123+ return;
124+ }
125+ if (enabled) {
126+ const ratio = 1.5;
127+ // 设置可布局内容宽高比限制
128+ mainWindow.setAspectRatio(ratio)
129+ .then(() => {
130+ AppStorage.setOrCreate('WINDOW_ASPECT_RATIO_ENABLED', enabled);
131+ this.resultText = '已开启比例限制';
132+ })
133+ .catch((err: Error) => {
134+ const businessError = err as BusinessError;
135+ this.resultText = `设置比例限制失败: ${JSON.stringify(businessError)}`;
136+ hilog.error(DOMAIN, TAG, `set aspect ratio failed: ${JSON.stringify(businessError)}`);
137+ });
138+ } else {
139+ // 取消可布局内容宽高比限制
140+ mainWindow.resetAspectRatio()
141+ .then(() => {
142+ AppStorage.setOrCreate('WINDOW_ASPECT_RATIO_ENABLED', enabled);
143+ this.resultText = '已关闭比例限制';
144+ })
145+ .catch((err: Error) => {
146+ const businessError = err as BusinessError;
147+ this.resultText = `重置比例限制失败: ${JSON.stringify(businessError)}`;
148+ hilog.error(DOMAIN, TAG, `reset aspect ratio failed: ${JSON.stringify(businessError)}`);
149+ });
150+ }
151+ }
152+// [End aspectRatio]
153+ 
154+ private parsePositiveInt(value: string): number | undefined {
155+ const trimValue = value.trim();
156+ 
157+ if (!/^\d+$/.test(trimValue)) {
158+ return undefined;
159+ }
160+ const result = Number.parseInt(trimValue);
161+ 
162+ if (!Number.isFinite(result) || result <= 0) {
163+ return undefined;
164+ }
165+ return result;
166+ }
167+ 
168+ /**
169+ 为子窗口设置windowLimits
170+ */
171+ private setSubWindowLimits(): void {
172+ const subWindow = this.getSubWindow();
173+ if (!subWindow) {
174+ this.resultText = '子窗口未创建';
175+ return;
176+ }
177+ 
178+ const minWidth = this.parsePositiveInt(this.minWidthText);
179+ const maxWidth = this.parsePositiveInt(this.maxWidthText);
180+ const minHeight = this.parsePositiveInt(this.minHeightText);
181+ const maxHeight = this.parsePositiveInt(this.maxHeightText);
182+ if (
183+ minWidth === undefined ||
184+ maxWidth === undefined ||
185+ minHeight === undefined ||
186+ maxHeight === undefined
187+ ) {
188+ this.resultText = '输入非法:windowLimits 必须是正整数';
189+ return;
190+ }
191+ if (minWidth > maxWidth) {
192+ this.resultText = '输入非法:minWidth 不能大于 maxWidth';
193+ return;
194+ }
195+ if (minHeight > maxHeight) {
196+ this.resultText = '输入非法:minHeight 不能大于 maxHeight';
197+ return;
198+ }
199+ 
200+ subWindow.setWindowLimits({
201+ minWidth: minWidth,
202+ maxWidth: maxWidth,
203+ minHeight: minHeight,
204+ maxHeight: maxHeight
205+ })
206+ .then(() => {
207+ this.resultText =
208+ `设置成功:width[${minWidth}, ${maxWidth}], height[${minHeight}, ${maxHeight}]`;
209+ })
210+ .catch((err: Error) => {
211+ const businessError = err as BusinessError;
212+ this.resultText = `设置 windowLimits 失败: ${JSON.stringify(businessError)}`;
213+ hilog.error(DOMAIN, TAG, `set windowLimits failed: ${JSON.stringify(businessError)}`);
214+ });
215+ }
216+ 
217+ build() {
218+ Scroll() {
219+ Column() {
220+ Text('Main Window')
221+ .fontSize(28)
222+ .fontWeight(FontWeight.Bold)
223+ 
224+ Button(this.aspectRatioEnabled ? '关闭主窗比例限制' : '开启主窗比例限制')
225+ .width('80%')
226+ .height(40)
227+ .onClick(() => {
228+ this.setWindowAspectRatioEnabled(!this.aspectRatioEnabled);
229+ })
230+ 
231+ Button(this.showSubWindowEnabled ? '隐藏子窗' : '显示子窗')
232+ .width('80%')
233+ .height(40)
234+ .margin({ top: 12 })
235+ .onClick(() => {
236+ this.setSubWindowVisibilityEnabled(!this.showSubWindowEnabled);
237+ })
238+ 
239+ Button(this.resizeEnabled ? '关闭子窗拖拽大小' : '开启子窗拖拽大小')
240+ .width('80%')
241+ .height(40)
242+ .margin({ top: 12 })
243+ .onClick(() => {
244+ this.setSubWindowResizeEnabled(!this.resizeEnabled);
245+ })
246+ 
247+ Button(this.moveEnabled ? '关闭子窗拖拽位置' : '开启子窗拖拽位置')
248+ .width('80%')
249+ .height(40)
250+ .margin({ top: 12 })
251+ .onClick(() => {
252+ this.setSubWindowMoveEnabled(!this.moveEnabled);
253+ })
254+ 
255+ Column() {
256+ Text('子窗口 windowLimits')
257+ .fontSize(18)
258+ .fontWeight(FontWeight.Bold)
259+ .margin({ bottom: 12 })
260+ 
261+ Row() {
262+ Text('minWidth')
263+ .width(90)
264+ .fontSize(14)
265+ 
266+ TextInput({ text: this.minWidthText })
267+ .type(InputType.Number)
268+ .width(160)
269+ .onChange((value: string) => {
270+ this.minWidthText = value;
271+ })
272+ 
273+ Text('maxWidth')
274+ .width(90)
275+ .fontSize(14)
276+ 
277+ TextInput({ text: this.maxWidthText })
278+ .type(InputType.Number)
279+ .width(160)
280+ .onChange((value: string) => {
281+ this.maxWidthText = value;
282+ })
283+ }
284+ .margin({ top: 8 })
285+ 
286+ Row() {
287+ Text('minHeight')
288+ .width(90)
289+ .fontSize(14)
290+ 
291+ TextInput({ text: this.minHeightText })
292+ .type(InputType.Number)
293+ .width(160)
294+ .onChange((value: string) => {
295+ this.minHeightText = value;
296+ })
297+ 
298+ Text('maxHeight')
299+ .width(90)
300+ .fontSize(14)
301+ 
302+ TextInput({ text: this.maxHeightText })
303+ .type(InputType.Number)
304+ .width(160)
305+ .onChange((value: string) => {
306+ this.maxHeightText = value;
307+ })
308+ }
309+ .margin({ top: 8 })
310+ 
311+ Button('设置子窗口 windowLimits')
312+ .width('80%')
313+ .height(44)
314+ .margin({ top: 16 })
315+ .onClick(() => {
316+ this.setSubWindowLimits();
317+ })
318+ }
319+ .width('90%')
320+ .padding(16)
321+ .margin({ top: 24 })
322+ .border({
323+ width: 1,
324+ color: '#D0D7DE',
325+ radius: 12
326+ })
327+ .backgroundColor('#FFFFFF')
328+ 
329+ Text(this.resultText)
330+ .id('resultText')
331+ .fontSize(14)
332+ .fontColor('#333333')
333+ .margin({ top: 20 })
334+ .width('90%')
335+ }
336+ .width('100%')
337+ .padding({ top: 40, bottom: 40 })
338+ .alignItems(HorizontalAlign.Center)
339+ }
340+ .width('100%')
341+ .height('100%')
342+ .backgroundColor('#F5F7FA')
343+ }
344+}
@@ -0,0 +1,147 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+// [Start startMoving]
17+import { window } from '@kit.ArkUI';
18+import { BusinessError } from '@kit.BasicServicesKit';
19+import hilog from '@ohos.hilog';
20+ 
21+const DOMAIN = 0x0000;
22+const TAG = 'Sample_AdjustLayout';
23+ 
24+@Entry
25+@Component
26+struct SubWindowPage {
27+ @StorageProp('WINDOW_SIZE') windowSize: window.Size = {
28+ width: 600,
29+ height: 300
30+ };
31+ 
32+ @StorageLink('SUB_WINDOW_VISIBILITY_ENABLED') showSubWindowEnabled: boolean = false;
33+ @StorageProp('SUB_WINDOW_RESIZE_ENABLED') resizeEnabled: boolean = true;
34+ @StorageProp('SUB_WINDOW_MOVE_ENABLED') moveEnabled: boolean = true;
35+ 
36+ private isCompact(): boolean {
37+ return this.windowSize.width < 500 || this.windowSize.height < 240;
38+ }
39+ 
40+ /**
41+ 通过startMoving为子窗口接入系统拖拽移动
42+ */
43+ private startMoveSubWindow(): void {
44+ const subWindow = AppStorage.get<window.Window>('SUB_WINDOW');
45+ if (!subWindow) {
46+ return;
47+ }
48+ subWindow.startMoving().then(() => {
49+ hilog.info(DOMAIN, TAG, 'startMoving success');
50+ }).catch((err: Error) => {
51+ const businessError = err as BusinessError;
52+ hilog.error(DOMAIN, TAG, `startMoving failed: ${JSON.stringify(businessError)}`);
53+ });
54+ }
55+ 
56+ build() {
57+ Column() {
58+ Row() {
59+ Text(this.moveEnabled ? 'Sub Window - Move Enabled' : 'Sub Window - Move Disabled')
60+ .fontSize(14)
61+ .fontWeight(FontWeight.Bold)
62+ .fontColor(Color.White)
63+ Blank()
64+ Text(`${this.windowSize.width} × ${this.windowSize.height}`)
65+ .fontSize(12)
66+ .fontColor('#DDE7FF')
67+ }
68+ .height(44)
69+ .width('100%')
70+ .padding({ left: 16, right: 16 })
71+ .backgroundColor(this.moveEnabled ? '#2F5BEA' : '#666666')
72+ .onTouch((event: TouchEvent) => {
73+ if (event.type === TouchType.Down && this.moveEnabled) {
74+ this.startMoveSubWindow();
75+ }
76+ })
77+ 
78+ Column() {
79+ Text('当前子窗口大小')
80+ .fontSize(16)
81+ .fontWeight(FontWeight.Bold)
82+ 
83+ Text(`width = ${this.windowSize.width}px`)
84+ .fontSize(14)
85+ .margin({ top: 8 })
86+ 
87+ Text(`height = ${this.windowSize.height}px`)
88+ .fontSize(14)
89+ .margin({ top: 4 })
90+ 
91+ Text(this.resizeEnabled ? '大小拖拽:开启' : '大小拖拽:关闭')
92+ .fontSize(13)
93+ .fontColor('#666666')
94+ .margin({ top: 12 })
95+ 
96+ Text(this.moveEnabled ? '位置拖拽:开启' : '位置拖拽:关闭')
97+ .fontSize(13)
98+ .fontColor('#666666')
99+ .margin({ top: 4 })
100+ 
101+ if (this.isCompact()) {
102+ Text('Compact Layout')
103+ .fontSize(18)
104+ .fontWeight(FontWeight.Bold)
105+ .margin({ top: 16 })
106+ } else {
107+ Row() {
108+ Column() {
109+ Text('Left Panel')
110+ .fontSize(18)
111+ .fontWeight(FontWeight.Bold)
112+ }
113+ .width('45%')
114+ .height(80)
115+ .justifyContent(FlexAlign.Center)
116+ .backgroundColor('#EEF3FF')
117+ 
118+ Column() {
119+ Text('Right Panel')
120+ .fontSize(18)
121+ .fontWeight(FontWeight.Bold)
122+ }
123+ .width('55%')
124+ .height(80)
125+ .justifyContent(FlexAlign.Center)
126+ .backgroundColor('#FFFFFF')
127+ }
128+ .width('100%')
129+ .margin({ top: 16 })
130+ }
131+ }
132+ .width('100%')
133+ .layoutWeight(1)
134+ .justifyContent(FlexAlign.Center)
135+ .alignItems(HorizontalAlign.Center)
136+ }
137+ .width('100%')
138+ .height('100%')
139+ .backgroundColor('#FFFFFF')
140+ .border({
141+ width: 4,
142+ color: '#FF4D4F',
143+ radius: 0
144+ })
145+ }
146+}
147+// [End startMoving]
@@ -0,0 +1,67 @@
1+/*
2+ * Copyright (c) 2025 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+
16+{
17+ "module": {
18+ "name": "entry",
19+ "type": "entry",
20+ "description": "$string:module_desc",
21+ "mainElement": "EntryAbility",
22+ "deviceTypes": [
23+ "default",
24+ "tablet",
25+ "2in1"
26+ ],
27+ "deliveryWithInstall": true,
28+ "installationFree": false,
29+ "pages": "$profile:main_pages",
30+ "abilities": [
31+ {
32+ "name": "EntryAbility",
33+ "srcEntry": "./ets/entryability/EntryAbility.ets",
34+ "description": "$string:EntryAbility_desc",
35+ "icon": "$media:layered_image",
36+ "label": "$string:EntryAbility_label",
37+ "startWindowIcon": "$media:startIcon",
38+ "startWindowBackground": "$color:start_window_background",
39+ "exported": true,
40+ "skills": [
41+ {
42+ "entities": [
43+ "entity.system.home"
44+ ],
45+ "actions": [
46+ "ohos.want.action.home"
47+ ]
48+ }
49+ ]
50+ }
51+ ],
52+ "extensionAbilities": [
53+ {
54+ "name": "EntryBackupAbility",
55+ "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
56+ "type": "backup",
57+ "exported": false,
58+ "metadata": [
59+ {
60+ "name": "ohos.extension.backup",
61+ "resource": "$profile:backup_config"
62+ }
63+ ],
64+ }
65+ ]
66+ }
67+}
@@ -0,0 +1,8 @@
1+{
2+ "color": [
3+ {
4+ "name": "start_window_background",
5+ "value": "#FFFFFF"
6+ }
7+ ]
8+}
@@ -0,0 +1,8 @@
1+{
2+ "float": [
3+ {
4+ "name": "page_text_font_size",
5+ "value": "50fp"
6+ }
7+ ]
8+}
@@ -0,0 +1,16 @@
1+{
2+ "string": [
3+ {
4+ "name": "module_desc",
5+ "value": "module description"
6+ },
7+ {
8+ "name": "EntryAbility_desc",
9+ "value": "description"
10+ },
11+ {
12+ "name": "EntryAbility_label",
13+ "value": "label"
14+ }
15+ ]
16+}
@@ -0,0 +1,7 @@
1+{
2+ "layered-image":
3+ {
4+ "background" : "$media:background",
5+ "foreground" : "$media:foreground"
6+ }
7+}
@@ -0,0 +1,6 @@
1+{
2+ "src": [
3+ "pages/Index",
4+ "pages/SubWindowPage"
5+ ]
6+}
@@ -0,0 +1,8 @@
1+{
2+ "color": [
3+ {
4+ "name": "start_window_background",
5+ "value": "#000000"
6+ }
7+ ]
8+}
@@ -0,0 +1,16 @@
1+/*
2+ * Copyright (c) 2025 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+{
16+}
@@ -0,0 +1,182 @@
1+/*
2+ * Copyright (c) 2025 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+import hilog from '@ohos.hilog';
17+import { describe, it, expect } from '@ohos/hypium';
18+import { Driver, ON } from '@kit.TestKit';
19+import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
20+ 
21+const TAG = '[Sample_AdjustLayout]';
22+const DOMAIN = 0xFF00
23+const BUNDLE = 'AdjustLayout_';
24+ 
25+export default function abilityTest() {
26+ 
27+ describe('EntryAbilityTest', () => {
28+ /**
29+ * 打开应用
30+ */
31+ it(BUNDLE + 'StartAbility_001', 0, async (done: Function) => {
32+ hilog.info(DOMAIN, TAG, BUNDLE + "StartAbility_001, begin");
33+ let driver = Driver.create();
34+ let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
35+ try {
36+ await abilityDelegator.startAbility({
37+ bundleName: 'com.samples.adjustlayout',
38+ abilityName: 'EntryAbility'
39+ });
40+ } catch (exception) {
41+ hilog.info(DOMAIN, TAG, BUNDLE + `StartAbility_001 exception = ${JSON.stringify(exception)}`);
42+ expect().assertFail();
43+ }
44+ await driver.delayMs(1000);
45+ await driver.assertComponentExist(ON.text('Main Window'));
46+ done();
47+ hilog.info(DOMAIN, TAG, BUNDLE + 'StartAbility_001 end');
48+ })
49+ 
50+ /**
51+ * 点击开启主窗比例限制按钮
52+ */
53+ it(BUNDLE + 'ClickAspectRatioButton_001', 2, async () => {
54+ hilog.info(DOMAIN, TAG, BUNDLE + "ClickAspectRatioButton_001, begin");
55+ let driver = Driver.create();
56+ let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
57+ try {
58+ await abilityDelegator.startAbility({
59+ bundleName: 'com.samples.adjustlayout',
60+ abilityName: 'EntryAbility'
61+ });
62+ } catch (exception) {
63+ hilog.info(DOMAIN, TAG, BUNDLE + `ClickAspectRatioButton_001 exception = ${JSON.stringify(exception)}`);
64+ expect().assertFail();
65+ }
66+ await driver.delayMs(1000);
67+ await driver.assertComponentExist(ON.text('开启主窗比例限制'));
68+ let clickBtn = await driver.findComponent(ON.text('开启主窗比例限制'));
69+ await clickBtn.click();
70+ await driver.delayMs(1000);
71+ await driver.assertComponentExist(ON.text('关闭主窗比例限制'));
72+ 
73+ hilog.info(DOMAIN, TAG, BUNDLE + `ClickAspectRatioButton_001 end`);
74+ })
75+ 
76+ /**
77+ * 点击显示子窗按钮
78+ */
79+ it(BUNDLE + 'ClickShowSubWindowButton_001', 2, async () => {
80+ hilog.info(DOMAIN, TAG, BUNDLE + "ClickShowSubWindowButton_001, begin");
81+ let driver = Driver.create();
82+ let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
83+ try {
84+ await abilityDelegator.startAbility({
85+ bundleName: 'com.samples.adjustlayout',
86+ abilityName: 'EntryAbility'
87+ });
88+ } catch (exception) {
89+ hilog.info(DOMAIN, TAG, BUNDLE + `ClickShowSubWindowButton_001 exception = ${JSON.stringify(exception)}`);
90+ expect().assertFail();
91+ }
92+ await driver.delayMs(1000);
93+ await driver.assertComponentExist(ON.text('显示子窗'));
94+ let clickBtn = await driver.findComponent(ON.text('显示子窗'));
95+ await clickBtn.click();
96+ await driver.delayMs(1000);
97+ await driver.assertComponentExist(ON.text('隐藏子窗'));
98+ 
99+ hilog.info(DOMAIN, TAG, BUNDLE + `ClickShowSubWindowButton_001 end`);
100+ })
101+ 
102+ /**
103+ * 点击关闭子窗拖拽大小按钮
104+ */
105+ it(BUNDLE + 'ClickResizeButton_001', 2, async () => {
106+ hilog.info(DOMAIN, TAG, BUNDLE + "ClickResizeButton_001, begin");
107+ let driver = Driver.create();
108+ let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
109+ try {
110+ await abilityDelegator.startAbility({
111+ bundleName: 'com.samples.adjustlayout',
112+ abilityName: 'EntryAbility'
113+ });
114+ } catch (exception) {
115+ hilog.info(DOMAIN, TAG, BUNDLE + `ClickResizeButton_001 exception = ${JSON.stringify(exception)}`);
116+ expect().assertFail();
117+ }
118+ await driver.delayMs(1000);
119+ await driver.assertComponentExist(ON.text('关闭子窗拖拽大小'));
120+ let clickBtn = await driver.findComponent(ON.text('关闭子窗拖拽大小'));
121+ await clickBtn.click();
122+ await driver.delayMs(1000);
123+ await driver.assertComponentExist(ON.text('开启子窗拖拽大小'));
124+ 
125+ hilog.info(DOMAIN, TAG, BUNDLE + `ClickResizeButton_001 end`);
126+ })
127+ 
128+ /**
129+ * 点击关闭子窗拖拽位置按钮
130+ */
131+ it(BUNDLE + 'ClickMoveButton_001', 2, async () => {
132+ hilog.info(DOMAIN, TAG, BUNDLE + "ClickMoveButton_001, begin");
133+ let driver = Driver.create();
134+ let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
135+ try {
136+ await abilityDelegator.startAbility({
137+ bundleName: 'com.samples.adjustlayout',
138+ abilityName: 'EntryAbility'
139+ });
140+ } catch (exception) {
141+ hilog.info(DOMAIN, TAG, BUNDLE + `ClickMoveButton_001 exception = ${JSON.stringify(exception)}`);
142+ expect().assertFail();
143+ }
144+ await driver.delayMs(1000);
145+ await driver.assertComponentExist(ON.text('关闭子窗拖拽位置'));
146+ let clickBtn = await driver.findComponent(ON.text('关闭子窗拖拽位置'));
147+ await clickBtn.click();
148+ await driver.delayMs(1000);
149+ await driver.assertComponentExist(ON.text('开启子窗拖拽位置'));
150+ 
151+ hilog.info(DOMAIN, TAG, BUNDLE + `ClickMoveButton_001 end`);
152+ })
153+ 
154+ /**
155+ * 点击设置子窗口 windowLimits 按钮
156+ */
157+ it(BUNDLE + 'ClickSetWindowLimitsButton_001', 2, async () => {
158+ hilog.info(DOMAIN, TAG, BUNDLE + "ClickSetWindowLimitsButton_001, begin");
159+ let driver = Driver.create();
160+ let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
161+ try {
162+ await abilityDelegator.startAbility({
163+ bundleName: 'com.samples.adjustlayout',
164+ abilityName: 'EntryAbility'
165+ });
166+ } catch (exception) {
167+ hilog.info(DOMAIN, TAG, BUNDLE + `ClickSetWindowLimitsButton_001 exception = ${JSON.stringify(exception)}`);
168+ expect().assertFail();
169+ }
170+ await driver.delayMs(1000);
171+ // 找到设置按钮(按钮上的文本)
172+ let clickBtn = await driver.findComponent(ON.text('设置子窗口 windowLimits'));
173+ await clickBtn.click();
174+ await driver.delayMs(1000);
175+ // 通过 id 定位 resultText 组件,验证点击后有响应
176+ await driver.assertComponentExist(ON.id('resultText'));
177+ 
178+ hilog.info(DOMAIN, TAG, BUNDLE + `ClickSetWindowLimitsButton_001 end`);
179+ })
180+ 
181+ })
182+}
@@ -0,0 +1,19 @@
1+/*
2+ * Copyright (c) 2025 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+import abilityTest from './Ability.test';
16+ 
17+export default function testsuite() {
18+ abilityTest();
19+}
@@ -0,0 +1,31 @@
1+/*
2+ * Copyright (c) 2025 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+
16+{
17+ "module": {
18+ "name": "entry_test",
19+ "type": "feature",
20+ "deviceTypes": [
21+ "phone",
22+ "tablet",
23+ "2in1",
24+ "car",
25+ "wearable",
26+ "tv"
27+ ],
28+ "deliveryWithInstall": true,
29+ "installationFree": false
30+ }
31+}
@@ -0,0 +1,20 @@
1+/*
2+ * Copyright (c) 2025 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+
16+import localUnitTest from './LocalUnit.test';
17+ 
18+export default function testsuite() {
19+ localUnitTest();
20+}
@@ -0,0 +1,48 @@
1+/*
2+ * Copyright (c) 2025 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+
16+import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
17+ 
18+export default function localUnitTest() {
19+ describe('localUnitTest', () => {
20+ // Defines a test suite. Two parameters are supported: test suite name and test suite function.
21+ beforeAll(() => {
22+ // Presets an action, which is performed only once before all test cases of the test suite start.
23+ // This API supports only one parameter: preset action function.
24+ });
25+ beforeEach(() => {
26+ // Presets an action, which is performed before each unit test case starts.
27+ // The number of execution times is the same as the number of test cases defined by **it**.
28+ // This API supports only one parameter: preset action function.
29+ });
30+ afterEach(() => {
31+ // Presets a clear action, which is performed after each unit test case ends.
32+ // The number of execution times is the same as the number of test cases defined by **it**.
33+ // This API supports only one parameter: clear action function.
34+ });
35+ afterAll(() => {
36+ // Presets a clear action, which is performed after all test cases of the test suite end.
37+ // This API supports only one parameter: clear action function.
38+ });
39+ it('assertContain', 0, () => {
40+ // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
41+ let a = 'abc';
42+ let b = 'b';
43+ // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
44+ expect(a).assertContain(b);
45+ expect(a).assertEqual(a);
46+ });
47+ });
48+}
@@ -0,0 +1,38 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+
16+{
17+ "modelVersion": "6.0.0",
18+ "dependencies": {
19+ },
20+ "execution": {
21+ // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | false ]. Default: "normal" */
22+ // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */
23+ // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */
24+ // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */
25+ // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */
26+ // "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */
27+ },
28+ "logging": {
29+ // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
30+ },
31+ "debugging": {
32+ // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
33+ },
34+ "nodeOptions": {
35+ // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/
36+ // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/
37+ }
38+}
@@ -0,0 +1,21 @@
1+/*
2+ * Copyright (c) 2025 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+import { appTasks } from '@ohos/hvigor-ohos-plugin';
17+ 
18+export default {
19+ system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
20+ plugins: [] /* Custom plugin to extend the functionality of Hvigor. */
21+}
@@ -0,0 +1,24 @@
1+/*
2+ * Copyright (c) 2025 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+{
16+ "modelVersion": "6.0.0",
17+ "description": "Please describe the basic information.",
18+ "dependencies": {
19+ },
20+ "devDependencies": {
21+ "@ohos/hypium": "1.0.25",
22+ "@ohos/hamock": "1.0.0"
23+ }
24+}
@@ -0,0 +1,10 @@
1+# AdjustLayout示例测试用例归档
2+## 用例表
3+| 测试功能 | 预置条件 | 输入 | 预期输出 | 是否自动 | 测试结果 |
4+|------------| --- |----------------------|------------| --- |------|
5+| 拉起应用 |设备正常运行| | 成功拉起应用 | 是 | Pass |
6+| 设置主窗比例限制 |设备正常运行| 点击"开启主窗比例限制"按钮 | 主窗口保持宽高比1.5 | 是 | Pass |
7+| 显示/隐藏子窗 |设备正常运行| 点击"显示子窗/隐藏子窗"按钮 | 子窗口显示或隐藏 | 是 | Pass |
8+| 开启/关闭子窗拖拽大小 |设备正常运行| 点击拖拽大小控制按钮 | 子窗口可/不可拖拽调整大小 | 是 | Pass |
9+| 开启/关闭子窗拖拽位置 |设备正常运行| 点击拖拽位置控制按钮 | 子窗口可/不可拖拽移动位置 | 是 | Pass |
10+| 设置子窗windowLimits |设备正常运行| 输入尺寸限制参数并点击设置按钮 | 子窗口尺寸限制生效 | 是 | Pass |
@@ -12,6 +12,7 @@
12 * See the License for the specific language governing permissions and12 * See the License for the specific language governing permissions and
13 * limitations under the License.13 * limitations under the License.
14 */14 */
15+
15{16{
16 "modelVersion": "6.0.0",17 "modelVersion": "6.0.0",
17 "dependencies": {18 "dependencies": {