HarmonyOS 是否支持透明背景以及自定义动画的页面

需要实现一个全局的弹出页面,和普通页面的区别主要如下:

  1. 背景透明
  2. 动画是从底部上滑

效果如图所示。由于是全局弹出的页面,无法使用 bindSheet 的方式实现。在现有的 rotuer 框架上是否能实现该效果?

HarmonyOS 是否支持透明背景以及自定义动画的页面 -鸿蒙开发者社区

HarmonyOS
2025-01-09 16:09:56
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
zbw_apple

实现方案:

使用navigation作为路由框架时,实现透明页面只需要设置页面的NavDestinationMode属性为DIALOG模式

实现思路:

1.使用navigation作为页面跟容器

2.跳转NavDestination页面并设置其mode属性为NavDestinationMode.DIALOG

3.添加自定义转场动画(这里使用的组件转场,可根据实际需要替换为navigation的自定义转场)

核心代码:

使用navigation作为跟页面容器

import { window } from '@kit.ArkUI'

@Entry
@Component
struct Index {
  pageInfos: NavPathStack = new NavPathStack()

  build() {
    Navigation(this.pageInfos) {
      Column({ space: 8 }) {
        Button('使用navigation路由')
          .onClick(() => {
            this.pageInfos.pushPath({ name: 'RouterOpacityPage2' })
          })
      }.height('100%').width('100%').justifyContent(FlexAlign.Center)
    }
    .height('100%')
    .width('100%')
    .hideTitleBar(true)
    .hideBackButton(true)
    .hideToolBar(true)
  }
}

子页面设置当前的页面模式为DIALOG模式

@Builder
export function RouterOpacityPage2Builder(name: string, param: Object) {
  RouterOpacityPage2()
}

@Component
export struct RouterOpacityPage2 {
  @State opacityValue: number = 1
  pageInfos: NavPathStack = new NavPathStack()

  build() {
    NavDestination() {
      Column() {
        Column() {
          Text('页面2').fontSize(50).fontWeight(FontWeight.Bold)
        }.width('80%').height('60%').backgroundColor(Color.White).borderRadius(20)
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#60000000')
      .justifyContent(FlexAlign.Center)
      .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
      .opacity(this.opacityValue)
      // 这里的动画可以使用navigation的自定义转场来实现,这里主要针对透明页面效果,动画效果不深入探讨实现
      .transition(TransitionEffect.OPACITY.animation({ duration: 300 }))
    }
    // 这里设置当前页面模式为DIALOG模式,默认情况下DIALOG模式就是透明页面
    .mode(NavDestinationMode.DIALOG)
    .hideTitleBar(true)
    .onBackPressed(() => {
      // 与第一种实现方式一样,这里也是用显示动画实现消失动画,具体场景也可以根据自己需要替换为navigation的自定义转场动画实现
      animateTo({
        duration: 300, onFinish: () => {
          this.pageInfos.pop()
        }
      }, () => {
        this.opacityValue = 0
      })
      return true
    })
    .onReady((context: NavDestinationContext) => {
      this.pageInfos = context.pathStack
    })
  }
}

补充:上面实现了一个最简单案例,但是我们实际开发过程中会涉及到参数传递与持久化状态的问题,持久化版本代码如下:

入口:

Button('使用navigation带参数持久化')
  .onClick(() => {
    // 实现页面持久化需要使用navigation单例路由模式,当前暂无相关接口直接实现需要手动实现
    let homeIndex = this.pageInfos.getIndexByName('RouterOpacityPage2')
    if (homeIndex.length == 0) {
      this.pageInfos.pushPath({ name: 'RouterOpacityPage2', param: 10 }, false)
      return
    }
    this.pageInfos.moveIndexToTop(homeIndex.pop(), false)
  })

RouterOpacityPage2页面接受参数代码如下:
// RouterOpacityPage2.ets
import CommentComponent from '../component/CommentComponent'

@Builder
export function RouterOpacityPage2Builder(name: string, param: Object) {
  RouterOpacityPage2()
}

@Component
export struct RouterOpacityPage2 {
  @State translateY: string | number = 0
  @State initialIndex: number = 0
  pageInfos: NavPathStack = new NavPathStack()

  close() {
    // 这里是用显示动画实现消失动画,具体场景也可以根据自己需要替换为navigation的自定义转场动画实现
    animateTo({
      duration: 300, onFinish: () => {
        if (this.initialIndex) {
          // 实现页面持久化需要使用navigation单例路由模式,当前暂无相关接口直接实现需要手动实现
          let homeIndex = this.pageInfos.getIndexByName('HomePage')
          if (homeIndex.length == 0) {
            this.pageInfos.pushPath({ name: 'HomePage' }, false)
            return
          }
          this.pageInfos.moveIndexToTop(homeIndex.pop(), false)
        } else {
          this.pageInfos.pop()
        }
      }
    }, () => {
      this.translateY = '100%'
    })
  }

  build() {
    NavDestination() {
      Column() {
        Column() {
          CommentComponent({ initialIndex: this.initialIndex })
        }
        .width('100%')
        .height('70%')
        .backgroundColor(Color.White)
        .borderRadius({ topLeft: 20, topRight: 20 })
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
        .onClick(() => {
        })
      }
      .translate({ y: this.translateY })
      .animation({ duration: 300 })
      .width('100%')
      .height('100%')
      .backgroundColor('#30000000')
      .justifyContent(FlexAlign.End)
      .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
      // 这里的动画可以使用navigation的自定义转场来实现,这里主要针对透明页面效果,动画效果不深入探讨实现
      .transition(TransitionEffect.move(TransitionEdge.BOTTOM).animation({ duration: 300 }))
      .onClick(() => {
        this.close()
      })
    }
    // 这里设置当前页面模式为DIALOG模式,默认情况下DIALOG模式就是透明页面
    .mode(NavDestinationMode.DIALOG)
    .hideTitleBar(true)
    .onShown(() => {
      this.translateY = 0
      let paramArray = this.pageInfos.getParamByName('RouterOpacityPage2') as number[]
      this.initialIndex = paramArray[paramArray.length - 1]
    })
    .onBackPressed(() => {
      this.close()
      return true
    })
    .onReady((context: NavDestinationContext) => {
      this.pageInfos = context.pathStack
    })
  }
}

实现方案二:使用router+subWindow实现

router路由无法更改页面模式,所以无法直接实现透明页面,需要借助拉起子窗口的方案实现透明页面的效果。思路如下:

1.获取窗口实例

2.拉起一个子窗口并加载对应页面

3.设置子窗口背景透明

4.定义子窗口的关闭方案

核心代码

在Ability中获取windowStage实例

onWindowStageCreate(windowStage: window.WindowStage): void {
  // Main window is created, set main page for this ability
  windowStage.loadContent('pages/Index', (err) => {
  // 这里需要注意为了确保windowStage实例获取成功,我们最好在loadContent回调中回去,能保证页面加载成功的时候一定能讲windowStage实例存到AppStorage对象中
  AppStorage.setOrCreate('windowStage', windowStage)
});
}

创建一个子窗口作为页面载体,并加载

RouterOpacityPage页面
private windowClass: window.WindowStage | null = null

aboutToAppear(): void {
  this.windowClass = AppStorage.get('windowStage') as window.WindowStage
}

build() {
  ...
  Button('使用router路由')
    .onClick(() => {
      this.windowClass?.createSubWindow('routerOpacityPage', (err, win) => {
        win.setUIContent('pages/RouterOpacityPage');
        win.showWindow();
      })
    })
  ...
}

加载页面后,这时候出现的新页面发现并不是透明的,那么我们把页面跟容器设置背景颜色为透明,也没有效果,根因是窗口默认是不透明的,需要设置窗口背景色

@Entry
@Component
struct RouterOpacityPage {
  aboutToAppear(): void {
    // 设置当前窗口背景透明
    window.findWindow('routerOpacityPage').setWindowBackgroundColor('#00000000')
  }

  build() {
    ...
  }
}

需要注意的是,子窗口无法与主窗口事件交互,并且默认的手势返回也无法销毁,所以需要自己监听页面的返回手势来销毁子窗口来实现回到原页面的效果

onBackPress(): boolean | void {
  // 这里解释下为什么需要用显示动画,因为窗口消失的时候无法对窗口添加动画,在转场动画中动画结束回调不生效,所以只能通过显示动画来控制组件显影然后在结束回调同销毁窗口
  animateTo({
  duration: 300, onFinish: () => {
    window.findWindow('routerOpacityPage').destroyWindow().then((res) => {
      console.log('destroyWindow success')
    }).catch(() => {
      console.log('destroyWindow fail')
    })
  }
}, () => {
  this.opacityValue = 0
})
return true
}

RouterOpacityPage 完整代码如下:

import { window } from '@kit.ArkUI'

@Entry
@Component
struct RouterOpacityPage {
  @State opacityValue: number = 1

  aboutToAppear(): void {
    // 设置当前窗口背景透明
    window.findWindow('routerOpacityPage').setWindowBackgroundColor('#00000000')
  }

  onBackPress(): boolean | void {
    // 这里解释下为什么需要用显示动画,因为窗口消失的时候无法对窗口添加动画,在转场动画中动画结束回调不生效,所以只能通过显示动画来控制组件显影然后在结束回调同销毁窗口
    animateTo({
      duration: 300, onFinish: () => {
        window.findWindow('routerOpacityPage').destroyWindow().then((res) => {
          console.log('destroyWindow success')
        }).catch(() => {
          console.log('destroyWindow fail')
        })
      }
    }, () => {
      this.opacityValue = 0
    })
    return true
  }

  build() {
    Column() {
      Column() {
        Text('页面2').fontSize(50).fontWeight(FontWeight.Bold)
      }
      .backgroundColor(Color.White)
      .borderRadius(20)
      .width('80%')
      .height('60%')
      .justifyContent(FlexAlign.Center)
    }
    .opacity(this.opacityValue)
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
    .backgroundColor('#60000000')
    .transition(TransitionEffect.OPACITY.animation({ duration: 300 }))
  }
}

以上使用subWindow的方案实现了一个简单的透明页面效果,实际场景中可能还涉及到页面的持久化与参数传递

方案二页面持久化:

上面代码中,在退出页面的时候使用的window.destroyWindow()方法,会导致整个窗口实例销毁,无法保存页面中的状态,这里需要使用window.minimize()方法来隐藏子窗口,而不是销毁子窗口,相关代码如下:

import { window } from '@kit.ArkUI'
import CommentComponent from '../component/CommentComponent'

@Entry
@Component
struct OpacityPage {
  @State opacityValue: number = 1
  @State initialIndex: number = 0

  onBackPress(): boolean | void {
    this.closeSubWindow()
    return true
  }

  onPageShow(): void {
    this.opacityValue = 1
  }

  closeSubWindow() {
    animateTo({
      duration: 300, onFinish: () => {
        // 当转场动画结束的时候执行窗口隐藏效果,注意这里不能使用destroyWindow销毁当前窗口,因为窗口销毁会导致page状态消失
        window.findWindow('OpacityPage').minimize().then((res) => {
          console.log('minimizeWindow success')
        }).catch(() => {
          console.log('minimizeWindow fail')
        })
      }
    }, () => {
      this.opacityValue = 0
    })
  }

  build() {
    Column() {
      Column() {
        CommentComponent({ initialIndex: this.initialIndex })
      }
      .backgroundColor(Color.White)
      .borderRadius(20)
      .width('80%')
      .height('60%')
      .justifyContent(FlexAlign.Center)
      .onClick(() => {})
    }
    .opacity(this.opacityValue)
    .animation({ duration: 300 })
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
    .backgroundColor('#60000000')
    .transition(TransitionEffect.OPACITY.animation({ duration: 300 }))
    .onClick(() => {
      this.closeSubWindow()
    })
  }
}

因为窗口之前没有提供数据传递的API,所以无法直接传递页面参数;但是每个窗口都有自己的UIContext,可以通过UIContext获取其他窗口的router路由栈,并进行参数传递操作

分享
微博
QQ
微信
回复
2025-01-09 18:21:05
相关问题
HarmonyOS 自定义Dialog背景透明问题
1414浏览 • 1回复 待解决
弹窗打开、关闭动画是否支持自定义
2789浏览 • 1回复 待解决
华为手机是否支持自定义锁屏页面
4447浏览 • 1回复 待解决
CustomDialog不支持自定义动画
875浏览 • 2回复 待解决
自定义弹窗自定义转场动画
1498浏览 • 1回复 待解决
是否支持自定义装饰器
2401浏览 • 1回复 待解决
HarmonyOS 是否支持自定义升级弹窗
233浏览 • 1回复 待解决
HarmonyOS ArkWeb是否支持自定义UserAgent
789浏览 • 1回复 待解决
HarmonyOS 是否支持自定义装饰器?
591浏览 • 1回复 待解决
HarmonyOS 组件是否支持自定义事件
409浏览 • 1回复 待解决
HarmonyOS ArkTS是否支持自定义注解
466浏览 • 1回复 待解决
HarmonyOS 如何自定义相机背景
432浏览 • 1回复 待解决
HarmonyOS 如何设置自定义弹窗透明
409浏览 • 1回复 待解决
Grid组件scrollBar是否支持自定义
2649浏览 • 1回复 待解决
CustomDialog自定义动画
942浏览 • 1回复 待解决