一、鸿蒙的半模态弹窗没有找到从上往下出现的方式,所以手写一个类似效果的弹窗
二、效果:
点击弹窗"X"关闭弹窗、弹窗出现时点击非弹窗内容区域关闭弹窗
三、实现思路:
1、弹窗出现消失:获取设备窗口高度、用定位position控制,移出可视范围
2、弹窗出现消失行为的速度(动画)效果,用关键帧动画
3、点击非弹窗区域关闭页面-用一个大盒子作为底部遮罩,覆盖整个页面,页面内写一个小盒子放在大盒子的上面部位,布局用stack层叠布局,点击底部遮罩控制弹窗消失
四、代码实现:
import { HMRouter, HMRouterMgr } from '@hadss/hmrouter';
import { display, UIContext, window } from '@kit.ArkUI';
@Entry
@Component
export struct SheetBox {
uiContext: UIContext | undefined = undefined;
@State zhaoH: number = 0; //遮罩y轴位置
@State windowH: number = 0; //设备窗口高度
@State showOverlay: boolean = false;
// 获取窗口高度
getWindowHeight() {
try {
this.windowH = display.getDefaultDisplaySync().height
console.log(this.zhaoH+'test')
} catch (exception) {
console.error('Failed to obtain the default display object. Code: ' + JSON.stringify(exception))
}
}
@Builder
sheet() {
Stack({ alignContent: Alignment.Top }) {
// 使用Column作为遮罩层
Row(){}
.width('100%')
.height('120%')
// .backgroundColor(Color.Pink) // 遮罩颜色
.onClick(() => this.closeSheet())
Column() {
Row() {
Text('快捷入口')
Text('×').fontSize(25).fontColor('#8c8c8c')
.onClick(() => this.closeSheet())
}.justifyContent(FlexAlign.SpaceBetween).width('100%')
Row() {
// 内容
GridRow({breakpoints:{value:['200vp','300vp','400vp']}}) { //不写这一行就用默认栅格尺寸,这个自定义的意思是200以下xs,200-300之间sm,300-400之间md,还可以往后写就是lg xl xxl,这里400是最大的。
GridCol({span: {xs: 12, sm: 6, md: 4, lg: 2,}}){
Column(){
Row(){
Image($r('app.media.sheet_home')).width(30).height(30)
}.backgroundColor('#fff').borderRadius('50%').width(60).height(60).justifyContent(FlexAlign.Center).alignItems(VerticalAlign.Center)
Text('首页').fontSize(12).margin({top:10})
}
}.onClick(() => {HMRouterMgr.push({pageUrl:'Home/Home'})})
GridCol({span: {xs: 12, sm: 6, md: 4, lg: 2,}}){
Column(){
Row(){
Image($r('app.media.sheet_search')).width(30).height(30)
}.backgroundColor('#fff').borderRadius('50%').width(60).height(60).justifyContent(FlexAlign.Center).alignItems(VerticalAlign.Center)
Text('搜素').fontSize(12).margin({top:10})
}
}.onClick(() => {HMRouterMgr.push({pageUrl:'Home/Home'})})
GridCol({span: {xs: 12, sm: 6, md: 4, lg: 2,}}){
Column(){
Row(){
Image($r('app.media.sheet_car')).width(30).height(30)
}.backgroundColor('#fff').borderRadius('50%').width(60).height(60).justifyContent(FlexAlign.Center).alignItems(VerticalAlign.Center)
Text('购物车').fontSize(12).margin({top:10})
}
}.onClick(() => {HMRouterMgr.push({pageUrl:'Home/Home'})})
}.padding({top:10})
// 内容
}
}
.width('100%')
// 模糊效果-毛玻璃 BlurStyle.BACKGROUND_THIN
.backgroundBlurStyle(BlurStyle.BACKGROUND_THIN, { colorMode: ThemeColorMode.LIGHT, adaptiveColor: AdaptiveColor.DEFAULT, scale: 1.0 })
.shadow(ShadowStyle.OUTER_DEFAULT_XS)
.height(250)
.padding({
top: 50,
bottom: 15,
left: 15,
right: 15
})
.borderRadius({ bottomLeft: 16, bottomRight: 16 })
}
// .backgroundColor(Color.Gray)
.zIndex(98).position({top:this.zhaoH,right:-10})
}
closeSheet() {
if (!this.uiContext) {
return;
}
this.showOverlay = false;
this.zhaoH = -60;
this.uiContext.keyframeAnimateTo({
iterations: 1,
}, [
{
duration: 600,
event: () => {
this.zhaoH = -this.windowH;
}
}
]);
}
openSheet() {
if (!this.uiContext) {
return;
}
this.showOverlay = true;
this.zhaoH = -this.windowH;
this.uiContext.keyframeAnimateTo({
iterations: 1,
}, [
{
duration: 600,
event: () => {
this.zhaoH = -60;
}
}
]);
}
aboutToAppear() {
this.uiContext = this.getUIContext?.();
this.getWindowHeight()
this.zhaoH = -this.windowH
}
build() {
Column() {
this.sheet()
// 使用
Image($rawfile('orders_iconmore.png')).width(20)
.onClick(() => {
this.openSheet()
})
}
}
}
五、使用局限:
用定位实现不能拿过来直接用,需要和自己原来的代码进行调试,否则布局可能会乱
注意:因为用定位实现的,所以要把这个代码作为最后一个组件调用,否则弹窗会被覆盖。