文档管理中心
您当前浏览的HarmonyOS 5.0.1(API 13)文档归档不再维护,推荐您使用最新版本。详细请参考文档维护策略变更

模态转场

模态转场是新的界面覆盖在旧的界面上,旧的界面不消失的一种转场方式。

表1 模态转场接口

展开
接口 说明 使用场景
bindContentCover 弹出全屏的模态组件。 用于自定义全屏的模态展示界面,结合转场动画和共享元素动画可实现复杂转场动画效果,如缩略图片点击后查看大图。
bindSheet 弹出半模态组件。 用于半模态展示界面,如分享框。
bindMenu 弹出菜单,点击组件后弹出。 需要Menu菜单的场景,如一般应用的“+”号键。
bindContextMenu 弹出菜单,长按或者右键点击后弹出。 长按浮起效果,一般结合拖拽框架使用,如桌面图标长按浮起。
bindPopup 弹出Popup弹框。 Popup弹框场景,如点击后对某个组件进行临时说明。
if 通过if新增或删除组件。 用来在某个状态下临时显示一个界面,这种方式的返回导航需要由开发者监听接口实现。

使用bindContentCover构建全屏模态转场效果

bindContentCover接口用于为组件绑定全屏模态页面,在组件出现和消失时可通过设置转场参数ModalTransition添加过渡动效。

  1. 定义全屏模态转场效果bindContentCover

  2. 定义模态展示界面。

    收起
    自动换行
    深色代码主题
    复制
    1. // 通过@Builder构建模态展示界面
    2. @Builder MyBuilder() {
    3. Column() {
    4. Text('my model view')
    5. }
    6. // 通过转场动画实现出现消失转场动画效果,transition需要加在builder下的第一个组件
    7. .transition(TransitionEffect.translate({ y: 1000 }).animation({ curve: curves.springMotion(0.6, 0.8) }))
    8. }
  3. 通过模态接口调起模态展示界面,通过转场动画或者共享元素动画去实现对应的动画效果。

    收起
    自动换行
    深色代码主题
    复制
    1. // 模态转场控制变量
    2. @State isPresent: boolean = false;
    3. Button('Click to present model view')
    4. // 通过选定的模态接口,绑定模态展示界面,ModalTransition是内置的ContentCover转场动画类型,这里选择None代表系统不加默认动画,通过onDisappear控制状态变量变换
    5. .bindContentCover(this.isPresent, this.MyBuilder(), {
    6. modalTransition: ModalTransition.NONE,
    7. onDisappear: () => {
    8. if (this.isPresent) {
    9. this.isPresent = !this.isPresent;
    10. }
    11. }
    12. })
    13. .onClick(() => {
    14. // 改变状态变量,显示模态界面
    15. this.isPresent = !this.isPresent;
    16. })

完整示例代码和效果如下。

收起
自动换行
深色代码主题
复制
  1. import { curves } from '@kit.ArkUI';
  2. interface PersonList {
  3. name: string,
  4. cardnum: string
  5. }
  6. @Entry
  7. @Component
  8. struct BindContentCoverDemo {
  9. private personList: Array<PersonList> = [
  10. { name: '王**', cardnum: '1234***********789' },
  11. { name: '宋*', cardnum: '2345***********789' },
  12. { name: '许**', cardnum: '3456***********789' },
  13. { name: '唐*', cardnum: '4567***********789' }
  14. ];
  15. // 第一步:定义全屏模态转场效果bindContentCover
  16. // 模态转场控制变量
  17. @State isPresent: boolean = false;
  18. // 第二步:定义模态展示界面
  19. // 通过@Builder构建模态展示界面
  20. @Builder
  21. MyBuilder() {
  22. Column() {
  23. Row() {
  24. Text('选择乘车人')
  25. .fontSize(20)
  26. .fontColor(Color.White)
  27. .width('100%')
  28. .textAlign(TextAlign.Center)
  29. .padding({ top: 30, bottom: 15 })
  30. }
  31. .backgroundColor(0x007dfe)
  32. Row() {
  33. Text('+ 添加乘车人')
  34. .fontSize(16)
  35. .fontColor(0x333333)
  36. .margin({ top: 10 })
  37. .padding({ top: 20, bottom: 20 })
  38. .width('92%')
  39. .borderRadius(10)
  40. .textAlign(TextAlign.Center)
  41. .backgroundColor(Color.White)
  42. }
  43. Column() {
  44. ForEach(this.personList, (item: PersonList, index: number) => {
  45. Row() {
  46. Column() {
  47. if (index % 2 == 0) {
  48. Column()
  49. .width(20)
  50. .height(20)
  51. .border({ width: 1, color: 0x007dfe })
  52. .backgroundColor(0x007dfe)
  53. } else {
  54. Column()
  55. .width(20)
  56. .height(20)
  57. .border({ width: 1, color: 0x007dfe })
  58. }
  59. }
  60. .width('20%')
  61. Column() {
  62. Text(item.name)
  63. .fontColor(0x333333)
  64. .fontSize(18)
  65. Text(item.cardnum)
  66. .fontColor(0x666666)
  67. .fontSize(14)
  68. }
  69. .width('60%')
  70. .alignItems(HorizontalAlign.Start)
  71. Column() {
  72. Text('编辑')
  73. .fontColor(0x007dfe)
  74. .fontSize(16)
  75. }
  76. .width('20%')
  77. }
  78. .padding({ top: 10, bottom: 10 })
  79. .border({ width: { bottom: 1 }, color: 0xf1f1f1 })
  80. .width('92%')
  81. .backgroundColor(Color.White)
  82. })
  83. }
  84. .padding({ top: 20, bottom: 20 })
  85. Text('确认')
  86. .width('90%')
  87. .height(40)
  88. .textAlign(TextAlign.Center)
  89. .borderRadius(10)
  90. .fontColor(Color.White)
  91. .backgroundColor(0x007dfe)
  92. .onClick(() => {
  93. this.isPresent = !this.isPresent;
  94. })
  95. }
  96. .size({ width: '100%', height: '100%' })
  97. .backgroundColor(0xf5f5f5)
  98. // 通过转场动画实现出现消失转场动画效果
  99. .transition(TransitionEffect.translate({ y: 1000 }).animation({ curve: curves.springMotion(0.6, 0.8) }))
  100. }
  101. build() {
  102. Column() {
  103. Row() {
  104. Text('确认订单')
  105. .fontSize(20)
  106. .fontColor(Color.White)
  107. .width('100%')
  108. .textAlign(TextAlign.Center)
  109. .padding({ top: 30, bottom: 60 })
  110. }
  111. .backgroundColor(0x007dfe)
  112. Column() {
  113. Row() {
  114. Column() {
  115. Text('00:25')
  116. Text('始发站')
  117. }
  118. .width('30%')
  119. Column() {
  120. Text('G1234')
  121. Text('8时1分')
  122. }
  123. .width('30%')
  124. Column() {
  125. Text('08:26')
  126. Text('终点站')
  127. }
  128. .width('30%')
  129. }
  130. }
  131. .width('92%')
  132. .padding(15)
  133. .margin({ top: -30 })
  134. .backgroundColor(Color.White)
  135. .shadow({ radius: 30, color: '#aaaaaa' })
  136. .borderRadius(10)
  137. Column() {
  138. Text('+ 选择乘车人')
  139. .fontSize(18)
  140. .fontColor(Color.Orange)
  141. .fontWeight(FontWeight.Bold)
  142. .padding({ top: 10, bottom: 10 })
  143. .width('60%')
  144. .textAlign(TextAlign.Center)
  145. .borderRadius(15)// 通过选定的模态接口,绑定模态展示界面,ModalTransition是内置的ContentCover转场动画类型,这里选择DEFAULT代表设置上下切换动画效果,通过onDisappear控制状态变量变换。
  146. .bindContentCover(this.isPresent, this.MyBuilder(), {
  147. modalTransition: ModalTransition.DEFAULT,
  148. onDisappear: () => {
  149. if (this.isPresent) {
  150. this.isPresent = !this.isPresent;
  151. }
  152. }
  153. })
  154. .onClick(() => {
  155. // 第三步:通过模态接口调起模态展示界面,通过转场动画或者共享元素动画去实现对应的动画效果
  156. // 改变状态变量,显示模态界面
  157. this.isPresent = !this.isPresent;
  158. })
  159. }
  160. .padding({ top: 60 })
  161. }
  162. }
  163. }

使用bindSheet构建半模态转场效果

bindSheet属性可为组件绑定半模态页面,在组件出现时可通过设置自定义或默认的内置高度确定半模态大小。构建半模态转场动效的步骤基本与使用bindContentCover构建全屏模态转场动效相同。

完整示例和效果如下。

收起
自动换行
深色代码主题
复制
  1. @Entry
  2. @Component
  3. struct BindSheetDemo {
  4. // 半模态转场显示隐藏控制
  5. @State isShowSheet: boolean = false;
  6. private menuList: string[] = ['不要辣', '少放辣', '多放辣', '不要香菜', '不要香葱', '不要一次性餐具', '需要一次性餐具'];
  7. // 通过@Builder构建半模态展示界面
  8. @Builder
  9. mySheet() {
  10. Column() {
  11. Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
  12. ForEach(this.menuList, (item: string) => {
  13. Text(item)
  14. .fontSize(16)
  15. .fontColor(0x333333)
  16. .backgroundColor(0xf1f1f1)
  17. .borderRadius(8)
  18. .margin(10)
  19. .padding(10)
  20. })
  21. }
  22. .padding({ top: 18 })
  23. }
  24. .width('100%')
  25. .height('100%')
  26. .backgroundColor(Color.White)
  27. }
  28. build() {
  29. Column() {
  30. Text('口味与餐具')
  31. .fontSize(28)
  32. .padding({ top: 30, bottom: 30 })
  33. Column() {
  34. Row() {
  35. Row()
  36. .width(10)
  37. .height(10)
  38. .backgroundColor('#a8a8a8')
  39. .margin({ right: 12 })
  40. .borderRadius(20)
  41. Column() {
  42. Text('选择点餐口味和餐具')
  43. .fontSize(16)
  44. .fontWeight(FontWeight.Medium)
  45. }
  46. .alignItems(HorizontalAlign.Start)
  47. Blank()
  48. Row()
  49. .width(12)
  50. .height(12)
  51. .margin({ right: 15 })
  52. .border({
  53. width: { top: 2, right: 2 },
  54. color: 0xcccccc
  55. })
  56. .rotate({ angle: 45 })
  57. }
  58. .borderRadius(15)
  59. .shadow({ radius: 100, color: '#ededed' })
  60. .width('90%')
  61. .alignItems(VerticalAlign.Center)
  62. .padding({ left: 15, top: 15, bottom: 15 })
  63. .backgroundColor(Color.White)
  64. // 通过选定的半模态接口,绑定模态展示界面,style中包含两个参数,一个是设置半模态的高度,不设置时默认高度是Large,一个是是否显示控制条DragBar,默认是true显示控制条,通过onDisappear控制状态变量变换。
  65. .bindSheet(this.isShowSheet, this.mySheet(), {
  66. height: 300,
  67. dragBar: false,
  68. onDisappear: () => {
  69. this.isShowSheet = !this.isShowSheet;
  70. }
  71. })
  72. .onClick(() => {
  73. this.isShowSheet = !this.isShowSheet;
  74. })
  75. }
  76. .width('100%')
  77. }
  78. .width('100%')
  79. .height('100%')
  80. .backgroundColor(0xf1f1f1)
  81. }
  82. }

使用bindMenu实现菜单弹出效果

bindMenu为组件绑定弹出式菜单,通过点击触发。完整示例和效果如下。

收起
自动换行
深色代码主题
复制
  1. class BMD{
  2. value:ResourceStr = ''
  3. action:() => void = () => {}
  4. }
  5. @Entry
  6. @Component
  7. struct BindMenuDemo {
  8. // 第一步: 定义一组数据用来表示菜单按钮项
  9. @State items:BMD[] = [
  10. {
  11. value: '菜单项1',
  12. action: () => {
  13. console.info('handle Menu1 select')
  14. }
  15. },
  16. {
  17. value: '菜单项2',
  18. action: () => {
  19. console.info('handle Menu2 select')
  20. }
  21. },
  22. ]
  23. build() {
  24. Column() {
  25. Button('click')
  26. .backgroundColor(0x409eff)
  27. .borderRadius(5)
  28. // 第二步: 通过bindMenu接口将菜单数据绑定给元素
  29. .bindMenu(this.items)
  30. }
  31. .justifyContent(FlexAlign.Center)
  32. .width('100%')
  33. .height(437)
  34. }
  35. }

使用bindContextMenu实现菜单弹出效果

bindContextMenu为组件绑定弹出式菜单,通过长按或右键点击触发。

完整示例和效果如下。

收起
自动换行
深色代码主题
复制
  1. @Entry
  2. @Component
  3. struct BindContextMenuDemo {
  4. private menu: string[] = ['保存图片', '收藏', '搜一搜'];
  5. private pics: Resource[] = [$r('app.media.icon_1'), $r('app.media.icon_2')];
  6. // 通过@Builder构建自定义菜单项
  7. @Builder myMenu() {
  8. Column() {
  9. ForEach(this.menu, (item: string) => {
  10. Row() {
  11. Text(item)
  12. .fontSize(18)
  13. .width('100%')
  14. .textAlign(TextAlign.Center)
  15. }
  16. .padding(15)
  17. .border({ width: { bottom: 1 }, color: 0xcccccc })
  18. })
  19. }
  20. .width(140)
  21. .borderRadius(15)
  22. .shadow({ radius: 15, color: 0xf1f1f1 })
  23. .backgroundColor(0xf1f1f1)
  24. }
  25. build() {
  26. Column() {
  27. Row() {
  28. Text('查看图片')
  29. .fontSize(20)
  30. .fontColor(Color.White)
  31. .width('100%')
  32. .textAlign(TextAlign.Center)
  33. .padding({ top: 20, bottom: 20 })
  34. }
  35. .backgroundColor(0x007dfe)
  36. Column() {
  37. ForEach(this.pics, (item: Resource) => {
  38. Row(){
  39. Image(item)
  40. .width('100%')
  41. .draggable(false)
  42. }
  43. .padding({ top: 20, bottom: 20, left: 10, right: 10 })
  44. .bindContextMenu(this.myMenu, ResponseType.LongPress)
  45. })
  46. }
  47. }
  48. .width('100%')
  49. .alignItems(HorizontalAlign.Center)
  50. }
  51. }

使用bindPopUp实现气泡弹窗效果

bindpopup属性可为组件绑定弹窗,并设置弹窗内容,交互逻辑和显示状态。

完整示例和代码如下。

收起
自动换行
深色代码主题
复制
  1. @Entry
  2. @Component
  3. struct BindPopupDemo {
  4. // 第一步:定义变量控制弹窗显示
  5. @State customPopup: boolean = false;
  6. // 第二步:popup构造器定义弹框内容
  7. @Builder popupBuilder() {
  8. Column({ space: 2 }) {
  9. Row().width(64)
  10. .height(64)
  11. .backgroundColor(0x409eff)
  12. Text('Popup')
  13. .fontSize(10)
  14. .fontColor(Color.White)
  15. }
  16. .justifyContent(FlexAlign.SpaceAround)
  17. .width(100)
  18. .height(100)
  19. .padding(5)
  20. }
  21. build() {
  22. Column() {
  23. Button('click')
  24. // 第四步:创建点击事件,控制弹窗显隐
  25. .onClick(() => {
  26. this.customPopup = !this.customPopup;
  27. })
  28. .backgroundColor(0xf56c6c)
  29. // 第三步:使用bindPopup接口将弹窗内容绑定给元素
  30. .bindPopup(this.customPopup, {
  31. builder: this.popupBuilder,
  32. placement: Placement.Top,
  33. maskColor: 0x33000000,
  34. popupColor: 0xf56c6c,
  35. enableArrow: true,
  36. onStateChange: (e) => {
  37. if (!e.isVisible) {
  38. this.customPopup = false;
  39. }
  40. }
  41. })
  42. }
  43. .justifyContent(FlexAlign.Center)
  44. .width('100%')
  45. .height(437)
  46. }
  47. }

使用if实现模态转场

上述模态转场接口需要绑定到其他组件上,通过监听状态变量改变调起模态界面。同时,也可以通过if范式,通过新增/删除组件实现模态转场效果。

完整示例和代码如下。

收起
自动换行
深色代码主题
复制
  1. @Entry
  2. @Component
  3. struct ModalTransitionWithIf {
  4. private listArr: string[] = ['WLAN', '蓝牙', '个人热点', '连接与共享'];
  5. private shareArr: string[] = ['投屏', '打印', 'VPN', '私人DNS', 'NFC'];
  6. // 第一步:定义状态变量控制页面显示
  7. @State isShowShare: boolean = false;
  8. private shareFunc(): void {
  9. this.getUIContext()?.animateTo({ duration: 500 }, () => {
  10. this.isShowShare = !this.isShowShare;
  11. })
  12. }
  13. build(){
  14. // 第二步:定义Stack布局显示当前页面和模态页面
  15. Stack() {
  16. Column() {
  17. Column() {
  18. Text('设置')
  19. .fontSize(28)
  20. .fontColor(0x333333)
  21. }
  22. .width('90%')
  23. .padding({ top: 30, bottom: 15 })
  24. .alignItems(HorizontalAlign.Start)
  25. TextInput({ placeholder: '输入关键字搜索' })
  26. .width('90%')
  27. .height(40)
  28. .margin({ bottom: 10 })
  29. .focusable(false)
  30. List({ space: 12, initialIndex: 0 }) {
  31. ForEach(this.listArr, (item: string, index: number) => {
  32. ListItem() {
  33. Row() {
  34. Row() {
  35. Text(`${item.slice(0, 1)}`)
  36. .fontColor(Color.White)
  37. .fontSize(14)
  38. .fontWeight(FontWeight.Bold)
  39. }
  40. .width(30)
  41. .height(30)
  42. .backgroundColor('#a8a8a8')
  43. .margin({ right: 12 })
  44. .borderRadius(20)
  45. .justifyContent(FlexAlign.Center)
  46. Column() {
  47. Text(item)
  48. .fontSize(16)
  49. .fontWeight(FontWeight.Medium)
  50. }
  51. .alignItems(HorizontalAlign.Start)
  52. Blank()
  53. Row()
  54. .width(12)
  55. .height(12)
  56. .margin({ right: 15 })
  57. .border({
  58. width: { top: 2, right: 2 },
  59. color: 0xcccccc
  60. })
  61. .rotate({ angle: 45 })
  62. }
  63. .borderRadius(15)
  64. .shadow({ radius: 100, color: '#ededed' })
  65. .width('90%')
  66. .alignItems(VerticalAlign.Center)
  67. .padding({ left: 15, top: 15, bottom: 15 })
  68. .backgroundColor(Color.White)
  69. }
  70. .width('100%')
  71. .onClick(() => {
  72. // 第五步:改变状态变量,显示模态页面
  73. if(item.slice(-2) === '共享'){
  74. this.shareFunc();
  75. }
  76. })
  77. }, (item: string): string => item)
  78. }
  79. .width('100%')
  80. }
  81. .width('100%')
  82. .height('100%')
  83. .backgroundColor(0xfefefe)
  84. // 第三步:在if中定义模态页面,显示在最上层,通过if控制模态页面出现消失
  85. if(this.isShowShare){
  86. Column() {
  87. Column() {
  88. Row() {
  89. Row() {
  90. Row()
  91. .width(16)
  92. .height(16)
  93. .border({
  94. width: { left: 2, top: 2 },
  95. color: 0x333333
  96. })
  97. .rotate({ angle: -45 })
  98. }
  99. .padding({ left: 15, right: 10 })
  100. .onClick(() => {
  101. this.shareFunc();
  102. })
  103. Text('连接与共享')
  104. .fontSize(28)
  105. .fontColor(0x333333)
  106. }
  107. .padding({ top: 30 })
  108. }
  109. .width('90%')
  110. .padding({bottom: 15})
  111. .alignItems(HorizontalAlign.Start)
  112. List({ space: 12, initialIndex: 0 }) {
  113. ForEach(this.shareArr, (item: string) => {
  114. ListItem() {
  115. Row() {
  116. Row() {
  117. Text(`${item.slice(0, 1)}`)
  118. .fontColor(Color.White)
  119. .fontSize(14)
  120. .fontWeight(FontWeight.Bold)
  121. }
  122. .width(30)
  123. .height(30)
  124. .backgroundColor('#a8a8a8')
  125. .margin({ right: 12 })
  126. .borderRadius(20)
  127. .justifyContent(FlexAlign.Center)
  128. Column() {
  129. Text(item)
  130. .fontSize(16)
  131. .fontWeight(FontWeight.Medium)
  132. }
  133. .alignItems(HorizontalAlign.Start)
  134. Blank()
  135. Row()
  136. .width(12)
  137. .height(12)
  138. .margin({ right: 15 })
  139. .border({
  140. width: { top: 2, right: 2 },
  141. color: 0xcccccc
  142. })
  143. .rotate({ angle: 45 })
  144. }
  145. .borderRadius(15)
  146. .shadow({ radius: 100, color: '#ededed' })
  147. .width('90%')
  148. .alignItems(VerticalAlign.Center)
  149. .padding({ left: 15, top: 15, bottom: 15 })
  150. .backgroundColor(Color.White)
  151. }
  152. .width('100%')
  153. }, (item: string): string => item)
  154. }
  155. .width('100%')
  156. }
  157. .width('100%')
  158. .height('100%')
  159. .backgroundColor(0xffffff)
  160. // 第四步:定义模态页面出现消失转场方式
  161. .transition(TransitionEffect.OPACITY
  162. .combine(TransitionEffect.translate({ x: '100%' }))
  163. .combine(TransitionEffect.scale({ x: 0.95, y: 0.95 })))
  164. }
  165. }
  166. }
  167. }

在 指南 中进行搜索
请输入您想要搜索的关键词