未验证 提交 32abc52a 编写于 作者: O openharmony_ci 提交者: Gitee

!21378 新增案例【不需要翻译】

Merge pull request !21378 from duangavin123/master
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
- [列表上拉加载更多内容](list-pullup-loading-data.md) - [列表上拉加载更多内容](list-pullup-loading-data.md)
- [如何删除多选框选项](delete-checkboxgroup-items.md) - [如何删除多选框选项](delete-checkboxgroup-items.md)
- [像素单位转换](pixel-format-transfer.md) - [像素单位转换](pixel-format-transfer.md)
- [如何在UIAbility间进行跳转](jump-between-UIAbilities.md)
- [转场动画](transition-animation.md)
### 装饰器 ### 装饰器
- [控制页面刷新范围](overall-and-part-refresh.md) - [控制页面刷新范围](overall-and-part-refresh.md)
...@@ -40,6 +42,7 @@ ...@@ -40,6 +42,7 @@
- [如何实现沉浸模式](immersion-mode.md) - [如何实现沉浸模式](immersion-mode.md)
- [如何创建悬浮窗](float-window.md) - [如何创建悬浮窗](float-window.md)
- [保持屏幕常亮](keep-screen-on.md) - [保持屏幕常亮](keep-screen-on.md)
- [如何创建子窗口并与主窗口通信](subwindow-mainwindow-communication.md)
### 数据管理 ### 数据管理
- [用户首选项的基本使用](preferences-data-process.md) - [用户首选项的基本使用](preferences-data-process.md)
......
# UIAbility内和UIAbility间页面的跳转
## 场景介绍
UIAbility组件是系统调度的基本单元,为应用提供绘制界面的窗口。一个应用可以包含一个或多个UIAbility组件。例如,在支付应用中,可以将入口功能和收付款功能分别配置为独立的UIAbility。
对于开发者而言,可以根据具体场景选择单个还是多个UIAbility,划分建议如下:
* 如果希望在任务视图中看到一个任务,则建议使用一个UIAbility,多个页面的方式。
* 如果希望在任务视图中看到多个任务,或者需要同时开启多个窗口,则建议使用多个UIAbility开发不同的模块功能。
本例即为大家介绍如何基于Stage模型下的UIAbility开发,实现UIAbility内和UIAbility间页面的跳转与数据传递的功能。
## 效果呈现
本例最终效果如下:
![UIAbility](figures/UIAbility.gif)
## 运行环境
本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发:
- IDE: DevEco Studio 4.0 Beta1
- SDK: Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1)
## 实现思路
本篇案例是基于Stage模型下的UIAbility开发,实现UIAbility内和UIAbility间页面的跳转。
* UIAbility内页面的跳转:
entry模块中,通过添加页面路由router来实现,页面路由router根据页面url找到目标页面,从而实现跳转。
* UIAbility间页面的跳转--跳转到指定UIAbility的首页:
实现UIAbility间页面的跳转,需要启动另外一个UIAbility,可以通过UIAbilityContext的startAbility的方法来完成。
* UIAbility间页面的跳转--跳转到指定UIAbility的指定页面(非首页):
实现跳转到指定UIAbility的指定页面(非首页),就需要在跳转到指定UIAbility的首页的基础上,新建一个Second页面,使用UIAbilityContext.startAbilityForResult来实现。
## 开发步骤
由于本例重点介绍UIAbility之间的跳转,所以开发步骤会着重讲解相关实现,不相关的内容不做介绍,全量代码可参考完整代码章节。
1. 从实现效果看,UIAbility之间的跳转,都是通过点击每个页面的button后实现的,因此我们可以先构建一个按钮点击后调用的方法类:ButtonClickMethod。
具体代码如下:
```ts
// entry/src/main/ets/model/ButtonClickMethod.ets
...
// 按钮点击后调用的方法类
Class ButtonClickMethod{
...
}
export default new ButtonClickMethod();
```
2. UIAbility内页面的跳转。
* 实现UIAbility内页面的跳转,首先构建Index页面,Index页面由一个Image组件、两个Text组件、三个Button组件组成。
具体代码如下:
```ts
// entry/src/main/ets/pages/Index.ets
@Entry
@Component
struct Index {
@State text: string = '';
build() {
Column() {
Image($r('app.media.right'))
...
Text($r('app.string.main_index_page_name'))
...
// 条件渲染:当text的值不为空时,显示该组件
if (this.text !== '') {
Text(this.text)
...
}
// 导航到EntryAbility的Second Page按钮
Button($r('app.string.to_main_second_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
// 导航到SecondAbility的Index Page按钮
Button($r('app.string.to_second_index_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
// 导航到SecondAbility的Index Page按钮
Button($r('app.string.to_second_second_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
}
...
}
}
```
* 构建Second页面,该页面由一个Image组件、两个Text组件、一个Button组件组成。
具体代码如下:
```ts
// entry/src/main/ets/pages/Second.ets
@Entry
@Component
struct Second {
...
build() {
Column() {
Image($r('app.media.left'))
...
Text($r('app.string.main_second_page_name'))
...
Text(`${this.src}${this.count}`)
...
// 返回到EntryAbility的Index Page按钮
Button($r('app.string.back_main_index_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
}
...
}
}
```
* entry模块的Index和Second页面之间的跳转以及数据的传递,需要通过router来实现。
* 从EntryAbility首页跳转到Second页面:
1. 导入router模块, 向按钮点击后调用的方法类ButtonClickMethod中添加toEntryAbilitySecond方法,使用router.pushUrl实现跳转,同时通过params来向新页面传入参数。
具体代码如下:
```ts
// entry/src/main/ets/model/ButtonClickMethod.ets
import router from '@ohos.router';
// 按钮点击后调用的方法类
Class ButtonClickMethod{
// 导航entry模块的Second页面
toEntryAbilitySecond() {
router.pushUrl({
url: 'pages/Second',
params: {
src: textMessage,
count: CommonConstants.NUM_VALUES[0]
}
});
}
...
}
export default new ButtonClickMethod();
```
2. 点击“导航到EntryAbility的Second Page”按钮后,调用ButtonClickMethod类中的toEntryAbilitySecond方法,跳转到EntryAbility的Second页面。
具体代码如下:
```ts
// entry/src/main/ets/pages/Index.ets
...
@Entry
@Component
struct Index {
@State text: string = '';
@State bottomMargin: string = StyleConstants.MAIN_INDEX_BUTTON_MARGIN_BOTTOM;
build() {
Column() {
Image($r('app.media.right'))
...
Text($r('app.string.main_index_page_name'))
...
// 条件渲染:当text的值不为空时,显示该组件
if (this.text !== '') {
Text(this.text)
...
}
Button($r('app.string.to_main_second_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
.onClick(() => {
// 导航到EntryAbility的Second Page
ButtonClickMethod.toSecondAbilityIndex(context);
this.text = '';
this.bottomMargin = StyleConstants.MAIN_INDEX_BUTTON_MARGIN_BOTTOM;
})
Button($r('app.string.to_second_index_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
Button($r('app.string.to_second_second_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
}
...
}
}
```
* 从entry模块的EntryAbility的Second页面返回至EntryAbility首页:
向EntryAbility的Second页面导入router模块,同时给button添加oncClick事件,使用router.back实现返回至EntryAbility的index页面。
具体代码如下:
```ts
// entry/src/main/ets/pages/Second.ets
import router from '@ohos.router';
@Entry
@Component
struct Second {
...
build() {
Column() {
Image($r('app.media.left'))
...
Text($r('app.string.main_second_page_name'))
...
Text(`${this.src}${this.count}`)
...
Button($r('app.string.back_main_index_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
.onClick(() => {
// 返回到EntryAbility的Index Page
router.back();
})
}
...
}
}
```
3. 跳转到指定的UIAbility的首页。
* 实现跳转到指定UIAbility的首页,先构建另外一个模块,方法如下:
在“Project”窗口,右键点击“entry 文件夹”,选择“New > Module > Empty Ability > Next”,在“Module name”中给新建的模块命名为“device”,点击“Next”,在“Ability name”中给新建模块的Ability命名为“SecondAbility”,点击“Finish”。可以看到文件目录结构如下:
![device-modules](figures/device-module.png)
* 构建device模块下SecondAbility的Index页面,该页面由一个Image组件、两个Text组件、一个Button组件组成。
具体代码如下:
```ts
// device/src/main/ets/pages/Index.ets
@Entry
@Component
struct Index {
...
build() {
Column() {
Image($r('app.media.left'))
...
Text($r('app.string.second_index_page_name'))
...
Text(`${this.src}${this.count}`)
...
// 停止SecondAbility自身按钮
Button($r('app.string.terminate_second_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
}
...
}
}
```
* 从entry模块的EntryAbility首页跳转至device模块的SecondAbility首页:需要通过UIAbilityContext的startAbility方法来实现。
1. 在EntryAbility的Index页面获取UIAbilityContext。
> 使用UIAbilityContext中的方法,需要在对应的页面获取相应的UIAbilityContext。
具体代码如下:
```ts
// entry/src/main/ets/pages/Index.ets
...
// 获取UIAbilityContext
let context = getContext(this);
...
```
2. 在EntryAbility的Index页面中,点击“导航到SecondAbility的Index Page”按钮后,调用ButtonClickMethod类中的toSecondAbilityIndex方法,拉起SecondAbility的Index页面,同时通过params来向新页面传入参数。
* 向ButtonClickMethod类中添加toSecondAbilityIndex方法。
具体代码如下:
```ts
// entry/src/main/ets/model/ButtonClickMethod.ets
import router from '@ohos.router';
import Logger from '../common/utils/Logger';
// 按钮点击后调用的方法类
Class ButtonClickMethod{
...
// 导航device模块的Index页面
toSecondAbilityIndex(context) {
let want = {
'deviceId': '',
'bundleName': 'com.example.uiability',
'abilityName': 'SecondAbility',
'moduleName':'device',
'parameters': {
src: textMessage,
count: 45
}
};
context.startAbility(want).then(() => {
Logger.info(CommonConstants.TAG, `start second ability index page succeed with ${JSON.stringify(want)}`);
}).catch((error) => {
Logger.error(CommonConstants.TAG, `start second ability index page failedwith ${error.code}`);
});
}
...
}
export default new ButtonClickMethod();
```
* 在EntryAbility的Index页面中,给“导航到SecondAbility的Index Page”按钮添加onClick事件,调用ButtonClickMethod类中的toSecondAbilityIndex方法,实现到SecondAbility首页的跳转。
具体代码如下:
```ts
// entry/src/main/ets/pages/Index.ets
...
// 获取UIAbilityContext
let context = getContext(this);
@Entry
@Component
struct Index {
@State text: string = '';
@State bottomMargin: string = StyleConstants.MAIN_INDEX_BUTTON_MARGIN_BOTTOM;
build() {
Column() {
Image($r('app.media.right'))
...
Text($r('app.string.main_index_page_name'))
...
// 条件渲染:当text的值不为空时,显示该组件
if (this.text !== '') {
Text(this.text)
...
}
Button($r('app.string.to_main_second_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
Button($r('app.string.to_second_index_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
.onClick(() => {
// 导航到SecondAbility的Index页面
ButtonClickMethod.toSecondAbilityIndex(context);
this.text = '';
this.bottomMargin = StyleConstants.MAIN_INDEX_BUTTON_MARGIN_BOTTOM;
})
Button($r('app.string.to_second_second_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
}
...
}
}
```
* 在SecondAbility的Index页面,获取从EntryAbility的Index页面传递过来的自定义参数,并用一个Text文本展示从Index页面传递过来的数据。
具体代码如下:
```ts
// device/src/main/ets/pages/Index.ets
...
@Entry
@Component
struct Index {
// 获取从EntryAbility的Index页面传递过来的自定义参数
@State src: string = globalThis?.secondAbilityWant?.parameters?.src ?? '-';
@State count: number = globalThis?.secondAbilityWant?.parameters?.count ?? 0;
build() {
Column() {
Image($r('app.media.left'))
...
Text($r('app.string.second_index_page_name'))
...
// 用一个Text文本展示从Index页面传递过来的数据
Text(`${this.src}:${this.count}`)
...
// 停止SecondAbility自身按钮
Button($r('app.string.terminate_second_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
}
...
}
}
```
* 从device模块的SecondAbility首页返回到entry模块的EntryAbility首页:通过点击device模块的Index页面的“停止SecondAbility自身”按钮,使用UIAbilityContext.terminateSelf方法手动销毁Ability。
1. 给ButtonClickMethod类中添加toSecondAbilityIndex方法。
具体代码如下:
```ts
// entry/src/main/ets/model/ButtonClickMethod.ets
import router from '@ohos.router';
import Logger from '../common/utils/Logger';
// 按钮点击后调用的方法类
Class ButtonClickMethod{
...
// 停止SecondAbility自身
terminateSecondAbility(context) {
context.terminateSelf().then(() => {
Logger.info(CommonConstants.TAG, 'terminate second ability self succeed');
}).catch((error) => {
Logger.error(CommonConstants.TAG, `terminate second ability self failed with ${error.code}`);
});
}
...
}
export default new ButtonClickMethod();
```
2. 在SecondAbility的Index页面中,给“停止SecondAbility自身”按钮添加onClick事件,调用ButtonClickMethod类中的terminateSecondAbility方法,使用UIAbilityContext.terminateSelf方法手动销毁Ability,从而实现从SecondAbility的Index页面返回至entry的Index页面。
具体代码如下:
```ts
// device/src/main/ets/model/Index.ets
let context = getContext(this);
...
@Entry
@Component
struct Index {
// 获取从EntryAbility的Index页面传递过来的自定义参数
@State src: string = globalThis?.secondAbilityWant?.parameters?.src ?? '-';
@State count: number = globalThis?.secondAbilityWant?.parameters?.count ?? 0;
build() {
Column() {
Image($r('app.media.left'))
...
Text($r('app.string.second_index_page_name'))
...
// 用一个Text文本展示从EntryAbility的Index页面传递过来的数据
Text(`${this.src}:${this.count}`)
...
Button($r('app.string.terminate_second_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
.onClick(() => {
// 停止SecondAbility自身
ButtonClickMethod.terminateSecondAbility(context);
})
}
...
}
}
```
4. 跳转到指定UIAbility的指定页面(非首页)。
* 构建device模块下SecondAbility的Second页面。该页面由一个Image组件、两个Text组件、一个Button组件组成。
具体代码如下:
```ts
// device/src/main/ets/pages/Second.ets
@Entry
@Component
struct Index {
...
build() {
Column() {
Image($r('app.media.left'))
...
Text($r('app.string.second_second_page_name'))
...
// 用一个Text文本展示从EntryAbility的Index页面传递过来的数据
Text(`${this.src}${this.count}`)
...
// 停止SecondAbility自身且返回结果按钮
Button($r('app.string.terminate_second_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
}
...
}
}
```
* 从entry模块的EntryAbility的首页跳转至device模块的SecondAbility的Second页面:通过点击“导航到SecondAbility的Second Page”按钮后,调用ButtonClickMethod类中的toSecondAbilitySecond方法,拉起SecondAbility的Second页面。
1. 给ButtonClickMethod类中添加toSecondAbilitySecond方法,该方法中使用UIAbilityContext.startAbilityForResult来实现,并获取被拉起侧销毁后的返回结果。可以通过parameters来向被拉起方传递参数。
具体代码如下:
```ts
// entry/src/main/ets/model/ButtonClickMethod.ets
import router from '@ohos.router';
import Logger from '../common/utils/Logger';
let currentContext = getContext(this);
// 按钮点击后调用的方法类
Class ButtonClickMethod{
...
// 导航到SecondAbility的Second页面
toSecondAbilitySecond(context, callback) {
let want = {
'deviceId': '',
'bundleName': 'com.example.uiability',
'abilityName': 'SecondAbility',
'moduleName':'device',
'parameters': {
url: 'pages/Second',
src: textMessage,
count: 78
}
};
// 被拉起侧销毁后,在startAbilityForResult回调中可以获取到被拉起侧销毁时传递过来的AbilityResult
context.startAbilityForResult(want).then((result) => {
callback(result);
Logger.info(CommonConstants.TAG, `start second ability second page succeed with ${JSON.stringify(want)}`);
}).catch((error) => {
Logger.error(CommonConstants.TAG, `start second ability second page failed with ${error.code}`);
});
}
...
}
export default new ButtonClickMethod();
```
2. 在EntryAbility的Index页面中,给“导航到SecondAbility的Second Page”按钮添加onClick事件,调用ButtonClickMethod类中的toSecondAbilityIndex方法,实现到SecondAbility首页的跳转。
具体代码如下:
```ts
// entry/src/main/ets/pages/Index.ets
...
// 获取UIAbilityContext
let context = getContext(this);
@Entry
@Component
struct Index {
@State text: string = '';
@State bottomMargin: string = StyleConstants.MAIN_INDEX_BUTTON_MARGIN_BOTTOM;
build() {
Column() {
Image($r('app.media.right'))
...
Text($r('app.string.main_index_page_name'))
...
// 条件渲染:当text的值不为空时,显示该组件
if (this.text !== '') {
Text(this.text)
...
}
Button($r('app.string.to_main_second_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
Button($r('app.string.to_second_index_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
Button($r('app.string.to_second_second_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
.onClick(() => {
this.text = '';
this.bottomMargin = StyleConstants.MAIN_INDEX_BUTTON_MARGIN_BOTTOM;
// 导航到SecondAbility的Second页面
ButtonClickMethod.toSecondAbilitySecond(context, (abilityResult) => {
// 获取SecondAbility被销毁时传递的abilityResult
if (abilityResult.resultCode === CommonConstants.RESULT_CODE) {
let src: string = abilityResult?.want?.parameters?.src ?? '-';
let count: number = abilityResult?.want?.parameters?.count ?? 0;
this.text = `${src}${count}`;
this.bottomMargin = StyleConstants.BUTTON_MARGIN_BOTTOM;
}
});
})
}
...
}
}
```
* 从device模块的SecondAbility的Second页面,返回至entry模块的EntryAbility首页:通过点击“停止SecondAbility自身并返回结果”按钮,调用ButtonClickMethod类中的terminateSecondAbilityForResult方法,使用UIAbilityContext.terminateSelfWithResult方法,同时传入不同的resultCode和want,手动销毁Ability,成功后拉起侧会收到abilityResult的值, 通过Text的方式显示在界面上,从而实现从SecondAbility的Second页面返回至entry的Index页面。
1. 给ButtonClickMethod类中添加terminateSecondAbilityForResult方法。
具体代码如下:
```ts
// entry/src/main/ets/model/ButtonClickMethod.ets
import router from '@ohos.router';
import Logger from '../common/utils/Logger';
// 按钮点击后调用的方法类
Class ButtonClickMethod{
...
// 停止SecondAbility自身
terminateSecondAbilityForResult(context) {
let abilityResult = {
resultCode: CommonConstants.RESULT_CODE,
want: {
'parameters': {
src: returnMessage,
count: 99
}
}
};
// 停止SecondAbility自身,并将abilityResult返回给startAbilityForResult接口调用方
context.terminateSelfWithResult(abilityResult).then(() => {
Logger.info(CommonConstants.TAG, `terminate second ability self succeed with ${JSON.stringify(abilityResult)}`);
}).catch((error) => {
Logger.error(CommonConstants.TAG, `terminate second ability self failed with ${error.code}`);
});
}
...
}
export default new ButtonClickMethod();
```
2. 在SecondAbility的Index页面中,给“停止SecondAbility自身并返回结果”按钮添加onClick事件,调用ButtonClickMethod类中的terminateSecondAbilityForResult方法,手动销毁自身Ability。
具体代码如下:
```ts
// device/src/main/ets/pages/Second.ets
let context = getContext(this);
@Entry
@Component
struct Second {
// 用来接收parameters参数传过来的值
@State src: string = globalThis?.secondAbilityWant?.parameters?.src ?? '-';
@State count: number = globalThis?.secondAbilityWant?.parameters?.count ?? 0;
build() {
Column() {
Image($r('app.media.left'))
...
Text($r('app.string.second_second_page_name'))
...
Text(`${this.src}${this.count}`)
..
// 停止SecondAbility自身且返回结果按钮
Button($r('app.string.terminate_second_for_result_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
...
.onClick(() => {
// 停止SecondAbility自身且返回结果.
ButtonClickMethod.terminateSecondAbilityForResult(context);
})
}
...
}
}
```
## 完整代码
本例完整代码如下:
1. entry模块的代码:
* 公共常量类:entry/src/main/ets/common/constants/CommonConstants.ets
具体代码如下:
```ts
class CommonConstants {
TAG = '[ButtonClickMethod.ts]';
RESULT_CODE = 100;
}
export default new CommonConstants();
```
* 样式常量类:entry/src/main/ets/common/constants/StyleConstants.ets
具体代码如下:
```ts
class StyleConstants {
IMAGE_WIDTH = '78%';
IMAGE_HEIGHT = '25%';
IMAGE_MARGIN_TOP = '140vp';
IMAGE_MARGIN_BOTTOM = '55vp';
MAIN_INDEX_TEXT_MARGIN_BOTTOM = '148vp';
BUTTON_WIDTH = '87%';
BUTTON_HEIGHT = '5%';
BUTTON_MARGIN_BOTTOM = '12vp';
MAIN_INDEX_BUTTON_MARGIN_BOTTOM = '179vp';
TEXT_MARGIN_BOTTOM = '250vp';
FULL_PERCENT = '100%';
FONT_SIZE_BIG = 20;
FONT_WEIGHT = 500;
FONT_SIZE_SMALL = 16;
OPACITY = 0.6;
}
export default new StyleConstants();
```
* 按钮点击后调用的方法类:entry/src/main/ets/model/ButtonClickMethod.ets
具体代码如下:
```ts
import router from '@ohos.router';
import Logger from '../common/utils/Logger';
import CommonConstants from '../common/constants/CommonConstants';
let currentContext = getContext(this);
let textMessage: string = currentContext.resourceManager.getStringSync($r('app.string.text_message'));
let returnMessage: string = currentContext.resourceManager.getStringSync($r('app.string.return_message'));
// 按钮点击后调用的方法类
class ButtonClickMethod {
// 导航entry模块的Second页面
toEntryAbilitySecond() {
router.pushUrl({
url: 'pages/Second',
params: {
src: textMessage,
count: 12
}
});
}
// 导航device模块的Index页面
toSecondAbilityIndex(context) {
let want = {
'deviceId': '',
'bundleName': 'com.example.uiability',
'abilityName': 'SecondAbility',
'moduleName':'device',
'parameters': {
src: textMessage,
count: 45
}
};
context.startAbility(want).then(() => {
Logger.info(CommonConstants.TAG, `start second ability index page succeed with ${JSON.stringify(want)}`);
}).catch((error) => {
Logger.error(CommonConstants.TAG, `start second ability index page failedwith ${error.code}`);
});
}
// 导航到SecondAbility的Second页面
toSecondAbilitySecond(context, callback) {
let want = {
'deviceId': '',
'bundleName': 'com.example.uiability',
'abilityName': 'SecondAbility',
'moduleName':'device',
'parameters': {
url: 'pages/Second',
src: textMessage,
count: 78
}
};
// 被拉起侧销毁后,在startAbilityForResult回调中可以获取到被拉起侧销毁时传递过来的AbilityResult
context.startAbilityForResult(want).then((result) => {
callback(result);
Logger.info(CommonConstants.TAG, `start second ability second page succeed with ${JSON.stringify(want)}`);
}).catch((error) => {
Logger.error(CommonConstants.TAG, `start second ability second page failed with ${error.code}`);
});
}
// 停止SecondAbility自身
terminateSecondAbility(context) {
context.terminateSelf().then(() => {
Logger.info(CommonConstants.TAG, 'terminate second ability self succeed');
}).catch((error) => {
Logger.error(CommonConstants.TAG, `terminate second ability self failed with ${error.code}`);
});
}
// 停止SecondAbility自身并返回结果
terminateSecondAbilityForResult(context) {
let abilityResult = {
resultCode: CommonConstants.RESULT_CODE,
want: {
'parameters': {
src: returnMessage,
count: 99
}
}
};
// 停止SecondAbility自身,并将abilityResult返回给startAbilityForResult接口调用方
context.terminateSelfWithResult(abilityResult).then(() => {
Logger.info(CommonConstants.TAG, `terminate second ability self succeed with ${JSON.stringify(abilityResult)}`);
}).catch((error) => {
Logger.error(CommonConstants.TAG, `terminate second ability self failed with ${error.code}`);
});
}
}
export default new ButtonClickMethod();
```
* EntryAbility的Index页面:entry/src/main/ets/pages/Index.ets
具体代码如下:
```ts
import ButtonClickMethod from '../model/ButtonClickMethod';
import StyleConstants from '../common/constants/StyleConstants';
import CommonConstants from '../common/constants/CommonConstants';
// 获取EntryAbility的UIAbilityContext
let context = getContext(this);
@Entry
@Component
struct Index {
@State text: string = '';
@State bottomMargin: string = StyleConstants.MAIN_INDEX_BUTTON_MARGIN_BOTTOM;
build() {
Column() {
Image($r('app.media.right'))
.objectFit(ImageFit.Contain)
.width(StyleConstants.IMAGE_WIDTH)
.height(StyleConstants.IMAGE_HEIGHT)
.margin({
top: StyleConstants.IMAGE_MARGIN_TOP,
bottom: StyleConstants.IMAGE_MARGIN_BOTTOM
})
Text($r('app.string.main_index_page_name'))
.fontColor('#000')
.fontSize(StyleConstants.FONT_SIZE_BIG)
.fontWeight(StyleConstants.FONT_WEIGHT)
.margin({ bottom: this.bottomMargin })
// 条件渲染:当text的值不为空时,显示该组件
if (this.text !== '') {
Text(this.text)
.fontColor('#000')
.fontSize(StyleConstants.FONT_SIZE_SMALL)
.opacity(StyleConstants.OPACITY)
.margin({ bottom: StyleConstants.MAIN_INDEX_TEXT_MARGIN_BOTTOM })
}
// 导航到EntryAbility的Second Page按钮
Button($r('app.string.to_main_second_page_btn_text'),{ type: ButtonType.Capsule, stateEffect: true })
.backgroundColor($r('app.color.button_background_color'))
.width(StyleConstants.BUTTON_WIDTH)
.height(StyleConstants.BUTTON_HEIGHT)
.margin({ bottom: StyleConstants.BUTTON_MARGIN_BOTTOM })
.onClick(() => {
// 导航到EntryAbility的Second page
ButtonClickMethod.toEntryAbilitySecond();
this.text = '';
this.bottomMargin = StyleConstants.MAIN_INDEX_BUTTON_MARGIN_BOTTOM;
})
// 导航到SecondAbility的Index Page按钮
Button($r('app.string.to_second_index_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
.backgroundColor($r('app.color.button_background_color'))
.width(StyleConstants.BUTTON_WIDTH)
.height(StyleConstants.BUTTON_HEIGHT)
.margin({ bottom: StyleConstants.BUTTON_MARGIN_BOTTOM })
.onClick(() => {
// 导航到SecondAbility的Index Page
ButtonClickMethod.toSecondAbilityIndex(context);
this.text = '';
this.bottomMargin = StyleConstants.MAIN_INDEX_BUTTON_MARGIN_BOTTOM;
})
// 导航到SecondAbility的Index Page按钮
Button($r('app.string.to_second_second_page_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
.backgroundColor($r('app.color.button_background_color'))
.width(StyleConstants.BUTTON_WIDTH)
.height(StyleConstants.BUTTON_HEIGHT)
.onClick(() => {
this.text = '';
this.bottomMargin = StyleConstants.MAIN_INDEX_BUTTON_MARGIN_BOTTOM;
// 导航到SecondAbility的Second Page
ButtonClickMethod.toSecondAbilitySecond(context, (abilityResult) => {
// 获取SecondAbility被销毁时传递的abilityResult
if (abilityResult.resultCode === CommonConstants.RESULT_CODE) {
let src: string = abilityResult?.want?.parameters?.src ?? '-';
let count: number = abilityResult?.want?.parameters?.count ?? 0;
this.text = `${src}${count}`;
this.bottomMargin = StyleConstants.BUTTON_MARGIN_BOTTOM;
}
});
})
}
.width(StyleConstants.FULL_PERCENT)
.height(StyleConstants.FULL_PERCENT)
.backgroundColor($r('app.color.background_color'))
}
}
```
* EntryAbility的Second页面:entry/src/main/ets/pages/Second.ets
具体代码如下:
```ts
import router from '@ohos.router';
import StyleConstants from '../common/constants/StyleConstants';
@Entry
@Component
struct Second {
@State src: string = router?.getParams()?.['src'] ?? '-';
@State count: number = router?.getParams()?.['count'] ?? 0;
build() {
Column() {
Image($r('app.media.left'))
.objectFit(ImageFit.Contain)
.width(StyleConstants.IMAGE_WIDTH)
.height(StyleConstants.IMAGE_HEIGHT)
.margin({
top: StyleConstants.IMAGE_MARGIN_TOP,
bottom: StyleConstants.IMAGE_MARGIN_BOTTOM
})
Text($r('app.string.main_second_page_name'))
.fontColor('#000')
.fontSize(StyleConstants.FONT_SIZE_BIG)
.fontWeight(StyleConstants.FONT_WEIGHT)
.margin({ bottom: StyleConstants.BUTTON_MARGIN_BOTTOM })
// 用一个Text文本展示从EntryAbility的Index页面传递过来的数据
Text(`${this.src}${this.count}`)
.fontColor('ccc')
.fontSize(StyleConstants.FONT_SIZE_SMALL)
.opacity(StyleConstants.OPACITY)
.margin({ bottom: StyleConstants.TEXT_MARGIN_BOTTOM })
// 返回到EntryAbility的Index Page按钮
Button($r('app.string.back_main_index_page_btn_text'),{ type: ButtonType.Capsule, stateEffect: true })
.backgroundColor($r('app.color.button_background_color'))
.width(StyleConstants.BUTTON_WIDTH)
.height(StyleConstants.BUTTON_HEIGHT)
.onClick(() => {
// 返回到EntryAbility的Index Page
router.back();
})
}
.width(StyleConstants.FULL_PERCENT)
.height(StyleConstants.FULL_PERCENT)
.backgroundColor($r('app.color.background_color'))
}
}
```
2. device模块的代码:
* SecondAbility的Index页面:device/src/main/ets/pages/Index.ets
具体代码如下:
```ts
import ButtonClickMethod from '../../../../../entry/src/main/ets/model/ButtonClickMethod';
import StyleConstants from '../../../../../entry/src/main/ets/common/constants/StyleConstants';
// 获取SecondAbility的UIAbilityContext
let context = getContext(this);
@Entry
@Component
struct Index {
// 获取从EntryAbility的Index页面传递过来的自定义参数
@State src: string = globalThis?.secondAbilityWant?.parameters?.src ?? '-';
@State count: number = globalThis?.secondAbilityWant?.parameters?.count ?? 0;
build() {
Column() {
Image($r('app.media.left'))
.objectFit(ImageFit.Contain)
.width(StyleConstants.IMAGE_WIDTH)
.height(StyleConstants.IMAGE_HEIGHT)
.margin({
top: StyleConstants.IMAGE_MARGIN_TOP,
bottom: StyleConstants.IMAGE_MARGIN_BOTTOM
})
// 用一个Text文本展示从EntryAbility的Index页面传递过来的数据
Text($r('app.string.second_index_page_name'))
.fontColor('#000')
.fontSize(StyleConstants.FONT_SIZE_BIG)
.fontWeight(StyleConstants.FONT_WEIGHT)
.margin({ bottom: StyleConstants.BUTTON_MARGIN_BOTTOM })
// 用一个Text文本展示从Index页面传递过来的数据
Text(`${this.src}${this.count}`)
.fontColor('#000')
.fontSize(StyleConstants.FONT_SIZE_SMALL)
.opacity(StyleConstants.OPACITY)
.margin({ bottom: StyleConstants.TEXT_MARGIN_BOTTOM })
// 停止SecondAbility自身按钮
Button($r('app.string.terminate_second_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
.backgroundColor($r('app.color.button_background_color'))
.width(StyleConstants.BUTTON_WIDTH)
.height(StyleConstants.BUTTON_HEIGHT)
.onClick(() => {
// 停止SecondAbility自身
ButtonClickMethod.terminateSecondAbility(context);
})
}
.width(StyleConstants.FULL_PERCENT)
.height(StyleConstants.FULL_PERCENT)
.backgroundColor($r('app.color.background_color'))
}
}
```
* SecondAbility的Second页面:device/src/main/ets/pages/Second.ets
具体代码如下:
```ts
import ButtonClickMethod from '../../../../../entry/src/main/ets/model/ButtonClickMethod';
import StyleConstants from '../../../../../entry/src/main/ets/common/constants/StyleConstants';
let context = getContext(this);
@Entry
@Component
struct Second {
// 用来接收parameters参数传过来的值
@State src: string = globalThis?.secondAbilityWant?.parameters?.src ?? '-';
@State count: number = globalThis?.secondAbilityWant?.parameters?.count ?? 0;
build() {
Column() {
Image($r('app.media.left'))
.objectFit(ImageFit.Contain)
.width(StyleConstants.IMAGE_WIDTH)
.height(StyleConstants.IMAGE_HEIGHT)
.margin({
top: StyleConstants.IMAGE_MARGIN_TOP,
bottom: StyleConstants.IMAGE_MARGIN_BOTTOM
})
Text($r('app.string.second_second_page_name'))
.fontColor('#000')
.fontSize(StyleConstants.FONT_SIZE_BIG)
.fontWeight(StyleConstants.FONT_WEIGHT)
.margin({ bottom: StyleConstants.BUTTON_MARGIN_BOTTOM })
// 用一个Text文本展示从EntryAbility的Index页面传递过来的数据
Text(`${this.src}${this.count}`)
.fontColor('#000')
.fontSize(StyleConstants.FONT_SIZE_SMALL)
.opacity(StyleConstants.OPACITY)
.margin({ bottom: StyleConstants.TEXT_MARGIN_BOTTOM })
// 停止SecondAbility自身且返回结果按钮
Button($r('app.string.terminate_second_for_result_btn_text'), { type: ButtonType.Capsule, stateEffect: true })
.backgroundColor($r('app.color.button_background_color'))
.width(StyleConstants.BUTTON_WIDTH)
.height(StyleConstants.BUTTON_HEIGHT)
.onClick(() => {
// 停止SecondAbility自身且返回结果
ButtonClickMethod.terminateSecondAbilityForResult(context);
})
}
.width(StyleConstants.FULL_PERCENT)
.height(StyleConstants.FULL_PERCENT)
.backgroundColor($r('app.color.background_color'))
}
}
```
## 参考
- [UIAbility组件概述](../application-dev/application-models/uiability-overview.md)
- [UIAbility组件使用指导](../application-dev/reference/apis/js-apis-app-ability-uiAbility.md)
- [UIAbilityContext使用指导](../application-dev/reference/apis/js-apis-inner-application-uiAbilityContext.md)
- [router (页面路由)使用指导](../application-dev/reference/apis/js-apis-router.md)
- [Want使用指导](../application-dev/reference/apis/js-apis-application-want.md)
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
为便于理解,通过以下例子具体说明单层和多层状态变化: 为便于理解,通过以下例子具体说明单层和多层状态变化:
```ts ```ts
class ClassA { class ClassB {
public c: number; public c: number;
constructor(c: number) { constructor(c: number) {
...@@ -24,20 +24,20 @@ class ClassA { ...@@ -24,20 +24,20 @@ class ClassA {
} }
} }
class ClassB { class ClassA {
// ClassB成员变量的类型为ClassA,ClassA为被嵌套类 // ClassA成员变量的类型为ClassB,ClassB为被嵌套类
public a: ClassA; public b: ClassB;
constructor(a: ClassA) { constructor(b: ClassB) {
this.a = a; this.b = b;
} }
} }
b: ClassB a: ClassA
// 变量a为ClassB的成员变量,为第一层变量,所以变量a的状态变化即为第一层状态变化 // 变量b为ClassA的成员变量,为第一层变量,所以变量b的状态变化即为第一层状态变化
this.b.a = new ClassA(0) this.a.b = new ClassB(0)
// 变量c为被嵌套类ClassA的成员变量,变量c的状态变化即为第二层状态变化 // 变量c为被嵌套类ClassB的成员变量,变量c的状态变化即为第二层状态变化
this.b.a.c = 5 this.a.b.c = 5
``` ```
## 监听第一层状态变化 ## 监听第一层状态变化
......
...@@ -128,6 +128,7 @@ ...@@ -128,6 +128,7 @@
}); });
}; };
} }
export default new PreferenceModel();
``` ```
2. UI中主要包含两大部分:文本和输入框,按钮。将这两部分分别抽取为子组件,在主页中进行调用。具体代码如下: 2. UI中主要包含两大部分:文本和输入框,按钮。将这两部分分别抽取为子组件,在主页中进行调用。具体代码如下:
文本和输入框子组件: 文本和输入框子组件:
......
# 如何创建子窗口并与主窗口通信
## 场景介绍
应用开发过程中,经常需要创建弹窗(子窗口)用来承载跟当前内容相关的业务,比如电话应用的拨号弹窗;阅读应用中长按当前内容触发的编辑弹窗;购物应用经常出现的抽奖活动弹窗等。
本文为大家介绍如何创建子窗口并实现子窗口与主窗口的数据通信。
## 效果呈现
本例最终效果如下:
![subwindow-mainwindow-communication](figures/subwindow-mainwindow-communication.gif)
## 环境要求
本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发:
- IDE: DevEco Studio 4.0 Beta1
- SDK: Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1)
## 实现思路
本例关键特性及实现方案如下:
- 点击“创建子窗口”按钮创建子窗口:使用window模块的createSubWindow方法创建子窗口,在创建时设置子窗口的大小、位置、内容等。
- 子窗口可以拖拽:通过gesture属性为子窗口绑定PanGesture拖拽事件,使用moveWindowTo方法将窗口移动到拖拽位置,呈现拖拽效果。
- 点击主窗口的“子窗口数据+1”按钮,子窗口中的数据加1,反之亦然,即实现主窗口和子窗口间的数据通信:将数据变量存储在AppStorage中,在主窗口和子窗口中引用该数据,并通过@StorageLink与AppStorage中的数据进行双向绑定,从而实现主窗口和子窗口之间的数据联动。
> ![icon-note.gif](../device-dev/public_sys-resources/icon-note.gif) **说明:**
> 本文使用AppStorage实现主窗口和子窗口之间的数据传递,除此之外,Emitter和EventHub等方式也可以实现,开发者可以根据实际业务需要进行选择。
## 开发步骤
由于本例重点讲解子窗口的创建以及主窗口和子窗口之间的通信,所以开发步骤会着重讲解相关内容的开发,其余内容不做赘述,全量代码可参考完整代码章节。
1. 创建子窗口。
使用createSubWindow方法创建名为“hiSubWindow”的子窗口,并设置窗口的位置、大小、显示内容。将创建子窗口的动作放在自定义成员方法showSubWindow()中,方便后续绑定到按钮上。具体代码如下:
```ts
showSubWindow() {
// 创建应用子窗口。
this.windowStage.createSubWindow("hiSubWindow", (err, data) => {
if (err.code) {
console.error('Failed to create the subwindow. Cause: ' + JSON.stringify(err));
return;
}
this.sub_windowClass = data;
console.info('Succeeded in creating the subwindow. Data: ' + JSON.stringify(data));
// 子窗口创建成功后,设置子窗口的位置
this.sub_windowClass.moveWindowTo(300, 300, (err) => {
if (err.code) {
console.error('Failed to move the window. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in moving the window.');
});
// 设置子窗口的大小
this.sub_windowClass.resize(350, 350, (err) => {
if (err.code) {
console.error('Failed to change the window size. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in changing the window size.');
});
// 为子窗口加载对应的目标页面。
this.sub_windowClass.setUIContent("pages/SubWindow",(err) => {
if (err.code) {
console.error('Failed to load the content. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in loading the content.');
// 显示子窗口。
this.sub_windowClass.showWindow((err) => {
if (err.code) {
console.error('Failed to show the window. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in showing the window.');
});
this.sub_windowClass.setWindowBackgroundColor('#E8A027')
});
})
}
```
2. 实现子窗口可拖拽。
为页面内容绑定PanGesture拖拽事件,拖拽事件发生时获取到触摸点的位置信息,使用@Watch监听到位置变量的变化,然后调用窗口的moveWindowTo方法将窗口移动到对应位置,从而实现拖拽效果。
具体代码如下:
```ts
import window from '@ohos.window';
interface Position {
x: number,
y: number
}
@Entry
@Component
struct SubWindow{
...
// 创建位置变量,并使用@Watch监听,变量发生变化调用moveWindow方法移动窗口
@State @Watch("moveWindow") windowPosition: Position = { x: 0, y: 0 };
private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });
private subWindow: window.Window
// 通过悬浮窗名称“hiSubWindow”获取到创建的悬浮窗
aboutToAppear() {
this.subWindow = window.findWindow("hiSubWindow")
}
// 将悬浮窗移动到指定位置
moveWindow() {
this.subWindow.moveWindowTo(this.windowPosition.x, this.windowPosition.y);
}
build(){
Column(){
Text(`AppStorage保存的数据:${this.storData}`)
.fontSize(12)
.margin({bottom:10})
Button('主窗口数据+1')
.fontSize(12)
.backgroundColor('#A4AE77')
.onClick(()=>{
this.storData += 1
})
}
.height('100%')
.width('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.gesture(
PanGesture(this.panOption)
.onActionStart((event: GestureEvent) => {
console.info('Pan start');
})
// 发生拖拽时,获取到触摸点的位置,并将位置信息传递给windowPosition
.onActionUpdate((event: GestureEvent) => {
this.windowPosition.x += event.offsetX;
this.windowPosition.y += event.offsetY;
})
.onActionEnd(() => {
console.info('Pan end');
})
)
}
}
```
3. 实现主窗口和子窗口间的数据通信。本例中即实现点击主窗口的“子窗口数据+1”按钮,子窗口中的数据加1,反之亦然。本例使用应用全局UI状态存储AppStorage来实现对应效果。
- 在创建窗口时触发的onWindowStageCreate回调中将自定义数据变量“data”存入AppStorage。
```ts
onWindowStageCreate(windowStage: window.WindowStage) {
// 将自定义数据变量“data”存入AppStorage
AppStorage.SetOrCreate('data', 1);
...
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
```
- 在主窗口中定义变量“storData”,并使用@StorageLink将其与AppStorage中的变量“data”进行双向绑定,这样一来,“mainData”的变化可以传导至“data”,并且该变化可以被UI框架监听到,从而完成UI状态刷新。
```ts
...
// 使用@StorageLink将"mainData"与AppStorage中的变量"data"进行双向绑定
@StorageLink('data') mainData: number = 1;
...
build() {
Row() {
Column() {
Text(`AppStorage保存的数据:${this.mainData}`)
.margin({bottom:30})
Button('子窗口数据+1')
.backgroundColor('#A4AE77')
.margin({bottom:30})
.onClick(()=>{
// 点击,storData的值加1
this.mainData += 1
})
...
}
.width('100%')
}
.height('100%')
}
```
- 在主窗口中定义变量“subData”,并使用@StorageLink将其与AppStorage中的变量“data”进行双向绑定。由于主窗口的“mainData”也与“data”进行了绑定,因此,“mainData”的值可以通过“data”传递给“subData”,反之亦然。这样就实现了主窗口和子窗口之间的数据同步。
```ts
...
// 使用@StorageLink将"subData"与AppStorage中的变量"data"进行双向绑定
@StorageLink('data') subData: number = 1;
...
build(){
Column(){
Text(`AppStorage保存的数据:${this.subData}`)
.fontSize(12)
.margin({bottom:10})
Button('主窗口数据+1')
.fontSize(12)
.backgroundColor('#A4AE77')
.onClick(()=>{
// 点击,subData的值加1
this.subData += 1
})
}
...
}
```
## 完整代码
本例完整代码如下:
EntryAbility文件代码:
```ts
// EntryAbility.ts
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import hilog from '@ohos.hilog';
import UIAbility from '@ohos.app.ability.UIAbility';
import Want from '@ohos.app.ability.Want';
import window from '@ohos.window';
let sub_windowClass = null;
export default class EntryAbility extends UIAbility {
destroySubWindow() {
// 销毁子窗口。当不再需要子窗口时,可根据具体实现逻辑,使用destroy对其进行销毁。
sub_windowClass.destroyWindow((err) => {
if (err.code) {
console.error('Failed to destroy the window. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in destroying the window.');
});
}
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy() {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage) {
// 将自定义数据变量“data”存入AppStorage
AppStorage.SetOrCreate('data', 1);
AppStorage.SetOrCreate('window', windowStage);
// 为主窗口添加加载页面
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
onWindowStageDestroy() {
this.destroySubWindow();
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground() {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground() {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}
```
主窗口代码:
```ts
// Index.ets
import window from '@ohos.window';
@Entry
@Component
struct Index {
// 使用@StorageLink将"mainData"与AppStorage中的变量"data"进行双向绑定
@StorageLink('data') mainData: number = 1;
@StorageLink('window') storWindow:window.WindowStage = null
private windowStage = this.storWindow
private sub_windowClass = null
showSubWindow() {
// 创建应用子窗口。
this.windowStage.createSubWindow("hiSubWindow", (err, data) => {
if (err.code) {
console.error('Failed to create the subwindow. Cause: ' + JSON.stringify(err));
return;
}
this.sub_windowClass = data;
console.info('Succeeded in creating the subwindow. Data: ' + JSON.stringify(data));
// 子窗口创建成功后,设置子窗口的位置、大小及相关属性等。
this.sub_windowClass.moveWindowTo(300, 300, (err) => {
if (err.code) {
console.error('Failed to move the window. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in moving the window.');
});
this.sub_windowClass.resize(350, 350, (err) => {
if (err.code) {
console.error('Failed to change the window size. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in changing the window size.');
});
// 为子窗口加载对应的目标页面。
this.sub_windowClass.setUIContent("pages/SubWindow",(err) => {
if (err.code) {
console.error('Failed to load the content. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in loading the content.');
// 显示子窗口。
this.sub_windowClass.showWindow((err) => {
if (err.code) {
console.error('Failed to show the window. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in showing the window.');
});
this.sub_windowClass.setWindowBackgroundColor('#E8A027')
});
})
}
build() {
Row() {
Column() {
Text(`AppStorage保存的数据:${this.mainData}`)
.margin({bottom:30})
Button('子窗口数据+1')
.backgroundColor('#A4AE77')
.margin({bottom:30})
.onClick(()=>{
// 点击,storData的值加1
this.mainData += 1
})
Button('创建子窗口')
.backgroundColor('#A4AE77')
.onClick(()=>{
// 点击弹出子窗口
this.showSubWindow()
})
}
.width('100%')
}
.height('100%')
}
}
```
子窗口代码:
```ts
// SubWindow.ets
import window from '@ohos.window';
interface Position {
x: number,
y: number
}
@Entry
@Component
struct SubWindow{
// 使用@StorageLink将"subData"与AppStorage中的变量"data"进行双向绑定
@StorageLink('data') subData: number = 1;
// 创建位置变量,并使用@Watch监听,变量发生变化调用moveWindow方法移动窗口
@State @Watch("moveWindow") windowPosition: Position = { x: 0, y: 0 };
private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });
private subWindow: window.Window
// 通过悬浮窗名称“hiSubWindow”获取到创建的悬浮窗
aboutToAppear() {
this.subWindow = window.findWindow("hiSubWindow")
}
// 将悬浮窗移动到指定位置
moveWindow() {
this.subWindow.moveWindowTo(this.windowPosition.x, this.windowPosition.y);
}
build(){
Column(){
Text(`AppStorage保存的数据:${this.subData}`)
.fontSize(12)
.margin({bottom:10})
Button('主窗口数据+1')
.fontSize(12)
.backgroundColor('#A4AE77')
.onClick(()=>{
// 点击,subData的值加1
this.subData += 1
})
}
.height('100%')
.width('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.gesture(
PanGesture(this.panOption)
.onActionStart((event: GestureEvent) => {
console.info('Pan start');
})
// 发生拖拽时,获取到触摸点的位置,并将位置信息传递给windowPosition
.onActionUpdate((event: GestureEvent) => {
this.windowPosition.x += event.offsetX;
this.windowPosition.y += event.offsetY;
})
.onActionEnd(() => {
console.info('Pan end');
})
)
}
}
```
## 参考
- [窗口开发](../application-dev/windowmanager/application-window-stage.md)
- [AppStorage:应用全局的UI状态存储](../application-dev/quick-start/arkts-appstorage.md)
\ No newline at end of file
# 转场动画的使用(ArkTs)
## 场景介绍
日常在应用时,经常需要衔接两个场景,或者两个镜头画面之间进行切换,切换时需要呈现一种平滑过渡效果。
本例将为大家介绍下如何通过转场动画实现上述过渡效果。
## 效果呈现
本例最终效果如下:
| 场景 | 效果图 |
| ---------------------------------- | ----------------------------------------------------- |
| 页面间转场--底部滑入转场 | ![BottomTransition.gif](figures/BottomTransition.gif) |
| 页面间转场--自定义1:缩放动画转场 | ![](figures/CustomTransition.gif) |
| 页面间转场---自定义2:旋转动画转场 | ![](figures/FullCustomTransition.gif) |
| 组件内转场 | ![](figures/ComponentTransition.gif) |
| 共享元素转场 | ![](figures/SharePage.gif) |
## 运行环境
本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发:
- IDE: DevEco Studio 4.0 Beta1
- SDK: Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1)
## 实现思路
* 构建应用首页,主要由5个相同样式的功能菜单组成,通过添加路由实现主页面与对应功能页面的链接跳转。
* 功能页面的实现
* 页面间转场
* 底部滑入转场
通过给pageTransition()方法定义入场效果PageTransitionEnter以及出场效果PageTransitionExit,同时通过设置slide属性为SlideEffect.Bottom来实现从底部滑入动效。
* 缩放动画转场
通过设置pageTransition方法,配置进行配置转场参数。
* 旋转动画转场
在FullCustomTransition.ets的Column组件中添加TransitionElement组件,并且定义pageTransition方法。给Clomn组件添加opacity、scale、rotate属性,定义变量animValue用来控制Clomn组件的动效,在PageTransitionEnter和PageTransitionExit组件中动态改变myProgress的值,从而控制动画效果。
* 组件间转场
* 通过Image、Column、Text、Button等组件构建ComponentTransition.ets页面。
* 新建一个Image组件,并且添加两个transition属性,分别用于定义组件的插入动效和移除动效,从而实现组件间的转场。
* 设置变量isShow,用来控制上述步骤中Image组件的添加和移除,同时向Button组件的onClick添加animateTo方法,来使ComponentItem子组件动效生效。
* isShow默认状态为false,删除隐藏Image组件,同时删除动效生效。
* 当isShow状态更新为true时,插入Image组件,同时插入动效生效。
* 共享转场
通过给两个页面“SharedItem”和“SharePage” 的Image组件设置sharedTransition属性来实现,两个页面的组件配置为同一个id,则转场过程中会执行共享元素转场效果。
## 开发步骤
1. 创建主界面。
添加媒体资源至resources > base > media目录下。
![Transition-animation-tree.png](figures/Transition-animation-tree.png)
首页Index.ets引入首页列表常量数据:INDEX_ANIMATION_MODE(imgRes:设置按钮的背景图片,url:设置页面路由的地址),通过ForEach方法循环渲染列表常量数据。
具体代码如下:
```ts
// entry/src/main/ets/pages/Index.ets
// 引入列表常量数据INDEX_ANIMATION_MODE
export const INDEX_ANIMATION_MODE = [
{ imgRes: $r('app.media.bg_bottom_anim_transition'), url: 'pages/BottomTransition' },
{ imgRes: $r('app.media.bg_custom1_anim_transition'), url: 'pages/CustomTransition' },
{ imgRes: $r('app.media.bg_custom2_anim_transition'), url: 'pages/FullCustomTransition' },
{ imgRes: $r('app.media.bg_element_anim_transition'), url: 'pages/ComponentTransition' },
{ imgRes: $r('app.media.bg_share_anim_transition'), url: 'pages/ShareItem' }
];
...
Column() {
// ForEach循环渲染
ForEach(INDEX_ANIMATION_MODE, ({ imgRes , url }) => {
Row()
.backgroundImage(imgRes)
.backgroundImageSize(ImageSize.Cover)
.backgroundColor('#00000000')
.height(130)
.margin({ bottom: 30 })
.width('100%')
.borderRadius(32)
.onClick(() => {
router.pushUrl({ url: url })
})
}, item => JSON.stringify(item))
}
```
添加其它组件,以及样式,完成UI构建。
具体代码如下:
```ts
// entry/src/main/ets/pages/Index.ets
import router from '@ohos.router';
import hilog from '@ohos.hilog';
@Entry
@Component
struct Index {
build() {
Column() {
Text($r('app.string.main_page_title'))
.fontSize(30)
.fontWeight(FontWeight.Regular)
.width('100%')
.margin({ top: 13, bottom: 27,left: 24})
Scroll() {
Column() {
ForEach(INDEX_ANIMATION_MODE, ({ imgRes , url }) => {
Row()
.backgroundImage(imgRes)
.backgroundImageSize(ImageSize.Cover)
.backgroundColor('#00000000')
.height(130)
.margin({ bottom: 30 })
.width('100%')
.borderRadius(32)
.onClick(() => {
router.pushUrl({ url: url })
.catch(err => {
hilog.error(0xff00, '[ReadingRecorder]', `%{public}s, %{public}s`, err);
});
})
}, item => JSON.stringify(item))
}
}
.align(Alignment.Top)
.layoutWeight(1)
.scrollBar(BarState.Off)
}
.height('100%')
.backgroundColor('#F1F3F5')
.padding({left:12 , right:12})
}
}
```
2. 实现页面间转场。
* 效果1:底部滑入。
该效果的实现,主要是通过在BottomTransition.ets中设置全局pageTransition()方法,该方法中自定义入场效果PageTransitionEnter以及出场效果PageTransitionExit,同时通过设置slide属性为SlideEffect.Bottom来实现从底部滑入动效。
具体代码如下:
```ts
// entry/src/main/ets/pages/BottomTransition.ets
@Entry
@Component
struct BottomTransition {
private imgRes: string | Resource = $r('app.media.bg_transition');
private imgFit: ImageFit = ImageFit.Fill;
build() {
Column() {
Image(this.imgRes)
.objectFit(this.imgFit)
.width('100%')
.height('100%')
}
}
// 页面转场通过全局pageTransition方法进行配置转场参数
pageTransition() {
// PageTransitionEnter自定义入场效果:设置slide属性为SlideEffect.Bottom 表示入场时从屏幕下方滑入。
PageTransitionEnter({ duration: 600, curve: Curve.Smooth }).slide(SlideEffect.Bottom);
// PageTransitionExit自定义出场效果:设置slide属性为SlideEffect.Bottom 退场时从屏幕下方滑出。
PageTransitionExit({ duration: 600, curve: Curve.Smooth }).slide(SlideEffect.Bottom);
}
}
```
* 效果2:页面入场时淡入和放大,退场时从右下角滑出。
* 在CustomTransition.ets中设置全局pageTransition()方法。
* pageTransition方法中自定义入场效果PageTransitionEnter:透明度设置从0.2到1;x、y轴缩放从0变化到1。
* pageTransition方法中自定义出场效果PageTransitionExit: x、y轴的偏移量为500。
具体代码如下:
```ts
// entry/src/main/ets/pages/CustomTransition.ets
@Entry
@Component
struct CustomTransition {
private imgRes: string | Resource = $r('app.media.bg_transition');
private imgFit: ImageFit = ImageFit.Fill;
build() {
Column() {
Image(this.imgRes)
.objectFit(this.imgFit)
.width('100%')
.height('100%')
}
}
// 页面转场通过全局pageTransition方法进行配置转场参数
pageTransition() {
// 进场时透明度设置从0.2到1;x、y轴缩放从0变化到1
PageTransitionEnter({ duration: 600, curve: Curve.Smooth }).opacity(0.2).scale({ x: 0, y: 0 })
// 退场时x、y轴的偏移量为500
PageTransitionExit({ duration: 600, curve: Curve.Smooth }).translate({ x: 500, y: 500 })
}
}
```
* 效果3:页面入场时淡入和放大,同时顺时针旋转;退场时淡出和缩小,同时逆时针旋转。
* 在FullCustomTransition.ets中添加Column组件。
* 向Column组件添加属性:opacity、scale、rotate,来控制动效的淡入淡出、缩放以及旋转效果。
* 定义变量animValue用来,通过animValue值得变化来控制Column组件的动效。
* 在FullCustomTransition.ets中定义全局pageTransition()方法。
* pageTransition方法中自定义入场效果PageTransitionEnter。
​ animValue值实时变化,0 --> 1,从而渲染 入场时淡入、放大以及顺时针旋转效果。
* pageTransition方法中自定义出场效果PageTransitionExit。
​ animValue值实时变化,1 --> 0,从而渲染 出场时淡出、缩小以及逆时针旋转效果。
具体代码如下:
```ts
// entry/src/main/ets/pages/FullCustomTransition.ets
@Entry
@Component
struct FullCustomTransition {
@State animValue: number = 1;
private imgRes: string | Resource = $r('app.media.bg_transition');
private imgFit: ImageFit = ImageFit.Fill;
build() {
Column() {
Image(this.imgRes)
.objectFit(this.imgFit)
.width('100%')
.height('100%')
}
// 设置淡入、淡出效果
.opacity(this.animValue)
// 设置缩放
.scale({ x: this.animValue, y: this.animValue })
// 设置旋转角度
.rotate({
z: 1,
angle: 360 * this.animValue
})
}
// 页面转场通过全局pageTransition方法进行配置转场参数
pageTransition() {
PageTransitionEnter({ duration: 600, curve: Curve.Smooth })
// 进场过程中会逐帧触发onEnter回调,入参为动效的归一化进度(0 - 1)
.onEnter((type: RouteType, progress: number) => {
// 入场动效过程中,实时更新this.animValue的值
this.animValue = progress
});
PageTransitionExit({ duration: 600, curve: Curve.Smooth })
// 出场过程中会逐帧触发onExit回调,入参为动效的归一化进度(0 - 1)
.onExit((type: RouteType, progress: number) => {
// 入场动效过程中,实时更新this.animValue的值
this.animValue = 1 - progress
});
}
}
```
3. 实现组件内转场。
* 通过Image、Column、Text、Button等组件构建ComponentTransition.ets页面。
具体代码如下:
```ts
// entry/src/main/ets/pages/ComponentTransition.ets
@Entry
@Component
struct ComponentTransition {
build() {
Column() {
Row() {
Image($r('app.media.ic_public_back'))
.width(20)
.height(20)
.responseRegion({width:'100%',height: '100%'})
.onClick(() => {
router.back();
})
Text($r('app.string.Component_transition_header'))
.fontColor(Color.Black)
.fontWeight(FontWeight.Regular)
.fontSize(25)
.margin({left:18,right:18})
}
.height(30)
.width('100%')
.margin({ top: 20, bottom: 27,left: 24})
Image($r('app.media.bg_element'))
.objectFit(ImageFit.Fill)
.borderRadius(20)
.margin({ bottom: 20 })
.width('100%')
.height(300)
Button($r('app.string.Component_transition_toggle'))
.height(40)
.width(120)
.fontColor(Color.White)
.backgroundColor($r('app.color.light_blue'))
}
.padding({left:20,right:20})
.height('100%')
.width('100%')
}
}
```
* 新建一个Image组件,并且添加两个transition属性,分别用于定义组件的插入动效和移除动效,来实现组件转场间。
具体代码如下:
```ts
// entry/src/main/ets/pages/ComponentTransition.ets
...
Image($r('app.media.bg_share'))
.objectFit(ImageFit.Fill)
.borderRadius(20)
.margin({ bottom: 20 })
.height(300)
.width('100%')
// 插入动效
.transition({
type: TransitionType.Insert,
scale: { x: 0.5, y: 0.5 },
opacity: 0
})
// 删除隐藏动效
.transition({
type: TransitionType.Delete,
rotate: { x: 0, y: 1, z: 0, angle: 360 },
opacity: 0
})
```
- 设置变量isShow,用来控制上述步骤中Image组件的添加和移除,同时向Button组件的onClick添加animateTo方法,来使ComponentItem子组件动效生效。
* isShow默认状态为false,删除隐藏Image组件,同时删除动效生效。
* 当isShow状态更新为true时,插入Image组件,同时插入动效生效。
具体代码如下:
```ts
// entry/src/main/ets/pages/ComponentTransition.ets
...
@State isShow: boolean = false;
...
// isShow为True,插入Image组件,同时插入动效生效;isShow为False,删除隐藏Image组件,同时删除动效生效
if (this.isShow) {
Image($r('app.media.bg_share'))
.objectFit(ImageFit.Fill)
.borderRadius(20)
.margin({ bottom: 20 })
.height(300)
.width('100%')
// 插入动效
.transition({
type: TransitionType.Insert,
scale: { x: 0.5, y: 0.5 },
opacity: 0
})
// 删除隐藏动效
.transition({
type: TransitionType.Delete,
rotate: { x: 0, y: 1, z: 0, angle: 360 },
opacity: 0
})
}
...
Button($r('app.string.Component_transition_toggle'))
...
.onClick(() => {
animateTo({ duration: 600 }, () => {
this.isShow = !this.isShow;
})
})
```
ComponentTransition.ets的完整代码如下:
```ts
// entry/src/main/ets/pages/ComponentTransition.ets
import router from '@ohos.router';
@Entry
@Component
struct ComponentTransition {
@State isShow: boolean = false;
build() {
Column() {
// 页面title区域,含返回功能以及title显示
Row() {
Image($r('app.media.ic_public_back'))
.width(20)
.height(20)
.responseRegion({
width:'100%',
height: '100%'
})
.onClick(() => {
router.back();
})
Text($r('app.string.Component_transition_header'))
.fontColor(Color.Black)
.fontWeight(FontWeight.Regular)
.fontSize(25)
.height(300)
.margin({ left:18, right:18 })
}
.height(30)
.width('100%')
.margin({ top: 20, bottom: 27,left: 24})
// 页面内容区域
// isShow为True,插入Image组件,同时插入动效生效;isShow为False,删除隐藏Image组件,同时删除动效生效
if (this.isShow) {
Image($r('app.media.bg_share'))
.objectFit(ImageFit.Fill)
.borderRadius(20)
.margin({ bottom: 20 })
.height(300)
.width('100%')
// 插入动效
.transition({
type: TransitionType.Insert,
scale: { x: 0.5, y: 0.5 },
opacity: 0
})
// 删除隐藏动效
.transition({
type: TransitionType.Delete,
rotate: { x: 0, y: 1, z: 0, angle: 360 },
opacity: 0
})
}
Image($r('app.media.bg_element'))
.objectFit(ImageFit.Fill)
.borderRadius(20)
.margin({ bottom: 20 })
.width('100%')
.height(300)
Button($r('app.string.Component_transition_toggle'))
.height(40)
.width(120)
.fontColor(Color.White)
.backgroundColor($r('app.color.light_blue'))
.onClick(() => {
animateTo({ duration: 600 }, () => {
console.log('console-- ' +this.isShow)
this.isShow = !this.isShow;
})
})
}
.padding({
left:(20),
right:(20)
})
.height('100%')
.width('100%')
}
}
```
4. 实现元素共享转场。
共享元素转场通过给组件设置sharedTransition属性来实现,两个页面的组件配置为同一个id,则转场过程中会执行共享元素转场效果。
* 通过Image、Column、Text等组件构建ShareItem.ets页面,给内容区域的Image组件设置sharedTransition属性标记该元素为共享元素,组件转场id设置为“shareID”, 同时设置共享元素转场效果。
具体代码如下:
```ts
// entry/src/main/ets/pages/ShareItem.ets
import hilog from '@ohos.hilog';
@Entry
@Component
struct ShareItem {
// 自定义页面内容区域
@Builder PreviewArea() {
Column() {
Image($r('app.media.bg_transition'))
.width('100%')
.height(300)
.borderRadius(24)
.margin({ bottom: 12 })
// 设置sharedTransition属性标记该元素为共享元素,转场id为“shareId”
.sharedTransition('shareId', {
duration: 600,
curve: Curve.Smooth,
delay: 100
})
.onClick(() => {
// 路由切换
router.pushUrl({ url: 'pages/SharePage' })
.catch(err => {
hilog.error(0xFF00, '[ReadingRecorder]', `%{public}s, %{public}s`, err);
});
})
Text($r('app.string.Share_Item_hint'))
.width('100%')
.textAlign(TextAlign.Center)
.fontSize(20)
.fontWeight(FontWeight.Regular)
.fontColor($r('app.color.share_item_content_font'))
}
.borderRadius(24)
.backgroundColor(Color.White)
.width('100%')
.padding({ top: 13, left: 12, right: 12,bottom:12})
}
build() {
Column() {
// 页面title区域,含返回功能以及title显示
Row() {
Image($r('app.media.ic_public_back'))
.width(20)
.height(20)
.responseRegion({
width:'100%',
height: '100%'
})
.onClick(() => {
router.back();
})
Text($r('app.string.Share_Item_header'))
.fontColor(Color.Black)
.fontWeight(FontWeight.Regular)
.fontSize(25)
.margin({ left:18, right:18 })
}
.height(30)
.width('100%')
.margin({ top: 20, bottom: 27,left: 24})
this.PreviewArea()
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.grey_light'))
.padding({left:12,right:12})
}
}
```
* pages/SharePage.ets页面中,给Image组件设置sharedTransition属性,同时组件转场id设置为“shareID”,从而可以共享上述步骤的转场动效。
具体代码如下:
```ts
// entry/src/main/ets/pages/SharePage.ets
@Entry
@Component
struct SharePage {
build() {
Column() {
Image($r('app.media.bg_transition'))
.objectFit(ImageFit.Fill)
.width('100%')
.height('100%')
.sharedTransition('shareId', {
duration: 600,
curve: Curve.Smooth,
delay: 100
})
}
}
}
```
## 完整代码
本例完整代码如下:
应用首页: /entry/src/main/ets/pages/Index.ets。
```ts
// entry/src/main/ets/pages/Index.ets
import router from '@ohos.router';
import hilog from '@ohos.hilog';
@Entry
@Component
struct Index {
build() {
Column() {
Text($r('app.string.main_page_title'))
.fontSize(30)
.fontWeight(FontWeight.Regular)
.width('100%')
.margin({ top: 13, bottom: 27,left: 24})
Scroll() {
Column() {
ForEach(INDEX_ANIMATION_MODE, ({ imgRes , url }) => {
Row()
.backgroundImage(imgRes)
.backgroundImageSize(ImageSize.Cover)
.backgroundColor('#00000000')
.height(130)
.margin({ bottom: 30 })
.width('100%')
.borderRadius(32)
.onClick(() => {
router.pushUrl({ url: url })
.catch(err => {
hilog.error(0xff00, '[ReadingRecorder]', `%{public}s, %{public}s`, err);
});
})
}, item => JSON.stringify(item))
}
}
.align(Alignment.Top)
.layoutWeight(1)
.scrollBar(BarState.Off)
}
.height('100%')
.backgroundColor('#F1F3F5')
.padding({left:12 , right:12})
}
}
```
底部滑出页面:/entry/src/main/ets/pages/BottomTransition.ets。
```ts
// entry/src/main/ets/pages/BottomTransition.ets
@Entry
@Component
struct BottomTransition {
private imgRes: string | Resource = $r('app.media.bg_transition');
private imgFit: ImageFit = ImageFit.Fill;
build() {
Column() {
Image(this.imgRes)
.objectFit(this.imgFit)
.width('100%')
.height('100%')
}
}
// 页面转场通过全局pageTransition方法进行配置转场参数
pageTransition() {
// PageTransitionEnter自定义入场效果:设置slide属性为SlideEffect.Bottom 表示入场时从屏幕下方滑入。
PageTransitionEnter({ duration: 600, curve: Curve.Smooth }).slide(SlideEffect.Bottom);
// PageTransitionExit自定义出场效果:设置slide属性为SlideEffect.Bottom 退场时从屏幕下方滑出。
PageTransitionExit({ duration: 600, curve: Curve.Smooth }).slide(SlideEffect.Bottom);
}
}
```
自定义1 缩放动画转场页面:/entry/src/main/ets/pages/CustomTransition.ets。
```ts
// entry/src/main/ets/pages/CustomTransition.ets
@Entry
@Component
struct CustomTransition {
private imgRes: string | Resource = $r('app.media.bg_transition');
private imgFit: ImageFit = ImageFit.Fill;
build() {
Column() {
Image(this.imgRes)
.objectFit(this.imgFit)
.width('100%')
.height('100%')
}
}
// 页面转场通过全局pageTransition方法进行配置转场参数
pageTransition() {
// 进场时透明度设置从0.2到1;x、y轴缩放从0变化到1
PageTransitionEnter({ duration: 600, curve: Curve.Smooth }).opacity(0.2).scale({ x: 0, y: 0 })
// 退场时x、y轴的偏移量为500
PageTransitionExit({ duration: 600, curve: Curve.Smooth }).translate({ x: 500, y: 500 })
}
}
```
自定义2 旋转动画转场: /entry/src/main/ets/pages/FullCustomTransition.ets。
```ts
@Entry
@Component
struct FullCustomTransition {
@State animValue: number = 1;
private imgRes: string | Resource = $r('app.media.bg_transition');
private imgFit: ImageFit = ImageFit.Fill;
build() {
Column() {
Image(this.imgRes)
.objectFit(this.imgFit)
.width('100%')
.height('100%')
}
// 设置淡入、淡出效果
.opacity(this.animValue)
// 设置缩放
.scale({ x: this.animValue, y: this.animValue })
// 设置旋转角度
.rotate({
z: 1,
angle: 360 * this.animValue
})
}
// 页面转场通过全局pageTransition方法进行配置转场参数
pageTransition() {
PageTransitionEnter({ duration: 600, curve: Curve.Smooth })
// 进场过程中会逐帧触发onEnter回调,入参为动效的归一化进度(0 - 1)
.onEnter((type: RouteType, progress: number) => {
// 入场动效过程中,实时更新this.animValue的值
this.animValue = progress
});
PageTransitionExit({ duration: 600, curve: Curve.Smooth })
// 出场过程中会逐帧触发onExit回调,入参为动效的归一化进度(0 - 1)
.onExit((type: RouteType, progress: number) => {
// 入场动效过程中,实时更新this.animValue的值
this.animValue = 1 - progress
});
}
}
```
组件内转场页面: /entry/src/main/ets/pages/ComponentTransition.ets。
```ts
import router from '@ohos.router';
@Entry
@Component
struct ComponentTransition {
@State isShow: boolean = false;
build() {
Column() {
// 页面title区域,含返回功能以及title显示
Row() {
Image($r('app.media.ic_public_back'))
.width(20)
.height(20)
.responseRegion({
width:'100%',
height: '100%'
})
.onClick(() => {
router.back();
})
Text($r('app.string.Component_transition_header'))
.fontColor(Color.Black)
.fontWeight(FontWeight.Regular)
.fontSize(25)
.margin({left:18, right:18})
}
.height(30)
.width('100%')
.margin({ top: 20, bottom: 27,left: 24})
// 页面内容区域
// isShow为True,插入Image组件,同时插入动效生效;isShow为False,删除隐藏Image组件,同时删除动效生效
if (this.isShow) {
Image($r('app.media.bg_share'))
.objectFit(ImageFit.Fill)
.borderRadius(20)
.margin({ bottom: 20 })
.height(300)
.width('100%')
// 插入动效
.transition({
type: TransitionType.Insert,
scale: { x: 0.5, y: 0.5 },
opacity: 0
})
// 删除隐藏动效
.transition({
type: TransitionType.Delete,
rotate: { x: 0, y: 1, z: 0, angle: 360 },
opacity: 0
})
}
Image($r('app.media.bg_element'))
.objectFit(ImageFit.Fill)
.borderRadius(20)
.margin({ bottom: 20 })
.width('100%')
.height(300)
Button($r('app.string.Component_transition_toggle'))
.height(40)
.width(120)
.fontColor(Color.White)
.backgroundColor($r('app.color.light_blue'))
.onClick(() => {
animateTo({ duration: 600 }, () => {
this.isShow = !this.isShow;
})
})
}
.padding({left:20,right:20})
.height('100%')
.width('100%')
}
}
```
共享元素转场部件:/entry/src/main/ets/pages/ShareItem.ets。
```ts
import hilog from '@ohos.hilog';
@Entry
@Component
struct ShareItem {
// 自定义页面内容区域
@Builder PreviewArea() {
Column() {
Image($r('app.media.bg_transition'))
.width('100%')
.height(300)
.borderRadius(24)
.margin({ bottom: 12 })
// 设置sharedTransition属性标记该元素为共享元素,转场id为“shareId”
.sharedTransition('shareId', {
duration: 600,
curve: Curve.Smooth,
delay: 100
})
.onClick(() => {
// 路由切换
router.pushUrl({ url: 'pages/SharePage' })
.catch(err => {
hilog.error(0xFF00, '[ReadingRecorder]', `%{public}s, %{public}s`, err);
});
})
Text($r('app.string.Share_Item_hint'))
.width('100%')
.textAlign(TextAlign.Center)
.fontSize(20)
.fontWeight(FontWeight.Regular)
.fontColor($r('app.color.share_item_content_font'))
}
.borderRadius(24)
.backgroundColor(Color.White)
.width('100%')
.padding({ top: 13, left: 12, right: 12,bottom:12})
}
build() {
Column() {
// 页面title区域,含返回功能以及title显示
Row() {
Image($r('app.media.ic_public_back'))
.width(20)
.height(20)
.responseRegion({
width:'100%',
height: '100%'
})
.onClick(() => {
router.back();
})
Text($r('app.string.Share_Item_header'))
.fontColor(Color.Black)
.fontWeight(FontWeight.Regular)
.fontSize(25)
.margin({ left:18, right:18 })
}
.height(30)
.width('100%')
.margin({ top: 20, bottom: 27,left: 24})
this.PreviewArea()
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.grey_light'))
.padding({left:12,right:12})
}
}
```
共享元素转场页面:/entry/src/main/ets/pages/SharePage.ets。
```ts
@Entry
@Component
struct SharePage {
build() {
Column() {
Image($r('app.media.bg_transition'))
.objectFit(ImageFit.Fill)
.width('100%')
.height('100%')
.sharedTransition('shareId', {
duration: 600,
curve: Curve.Smooth,
delay: 100
})
}
}
}
```
## 参考
- [图形变换](../application-dev/reference/arkui-ts/ts-universal-attributes-transformation.md)
- [页面间转场](../application-dev/reference/arkui-ts/ts-page-transition-animation.md)
- [组件内转场](../application-dev/ui/arkts-shared-element-transition.md)
- [共享元素转场](../application-dev/reference/arkui-ts/ts-transition-animation-shared-elements.md)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册