鸿蒙Next实现功能引导提示 原创 精华

auhgnixgnahz
发布于 2025-8-4 08:38
浏览
0收藏

当首次安装应用时,通常会有功能引导提示的需求,本文实现一个简单的功能引导案例供参考,也可以使用第三方的插件库@ohos/high_light_guide
实现效果:
鸿蒙Next实现功能引导提示-鸿蒙开发者社区

实现思路:
1.使用Stack布局在需要引导的布局上面增加一个蒙版
2.在蒙版中显示需要引导的内容,需要获取关键元素的位置,在蒙版中重新绘制高亮显示,再增加popup步骤提示弹框
3.通过提示步骤控制每个提示框的显示和隐藏,需要处理返回键、home键点击事件对popup的影响
核心方法: getInspectorByKey(id:string)
获取指定id的组件的所有属性,不包括子组件信息,返回组件属性列表的JSON字符串。

{
    "$type": "Button", // 组件类型
    "$ID": 44, // 组件id
    "type": "build-in", // build-in为系统组件,custom为自定义组件
    "$rect": "[498.00, 468.00],[718.00,598.00]", // 组件框左上角坐标和右下角坐标
    "$debugLine": "", // 组件对应源码的调试信息,包括源码路径和组件所在的行号。
    "$attrs": {
        "borderStyle": "BorderStyle.Solid",
        "borderColor": "#FF000000",
        "borderWidth": "0.00vp",
        "borderRadius": {
            "topLeft": "65.00px",
            "topRight": "65.00px",
            "bottomLeft": "65.00px",
            "bottomRight": "65.00px"
        },
        "border": "{\"style\":\"BorderStyle.Solid\",\"color\":\"#FF000000\",\"width\":\"0.00vp\",\"radius\":{\"topLeft\":\"65.00px\",\"topRight\":\"65.00px\",\"bottomLeft\":\"65.00px\",\"bottomRight\":\"65.00px\"},\"dashGap\":\"0.00vp\",\"dashWidth\":\"0.00vp\"}",
        "outlineStyle": "OutlineStyle.SOLID",
        "outlineColor": "#FF000000"
    }
}

源码:

import { AlignRules } from '../utils/AlignRules'
import { WindowUtils } from '../utils/WindowUtils';

@Entry
@ComponentV2
struct GuidePageTest{
  @Local guideStep:number=0
  @Local currentGuideStep:number=0;
  @Local showGuilde:boolean = false
  @Local mainMargin:Padding={top:0,left:0,right:0}
  tabs:string[]=['tab1','tab2','tab3','tab4','tab5'];
//popup提示内容
  @Builder
  popupBuilder(tips:string) {
    Column({ space: 10 }) {
      Text(tips).fontSize(15)
      Button(this.guideStep<4?'下一步':'完成').height(30).fontSize(13).onClick(()=>{
       this.currentGuideStep = ++this.guideStep
      })
    }.padding(10).zIndex(3)
  }
//蒙版内容
  @Builder
  showGuildComment(){
    Stack(){
      Text('第一步').fontSize(20).fontColor(Color.Black).position(componentGlobalPosition('text1')).backgroundColor(Color.White).padding(5).borderRadius(10)
        .visibility(this.guideStep==1?Visibility.Visible:Visibility.Hidden)
        .bindPopup(this.guideStep==1,{
          builder:this.popupBuilder('这是第一步提示'),
          placement:Placement.Bottom,
          backgroundBlurStyle:BlurStyle.NONE,
          autoCancel:false,
          popupColor:Color.White,
          onWillDismiss:false
        })
      Text('第二步').fontSize(20).fontColor(Color.Black).position(componentGlobalPosition('text2')).backgroundColor(Color.White).padding(5).borderRadius(10)
        .visibility(this.guideStep==2?Visibility.Visible:Visibility.Hidden)
        .bindPopup(this.guideStep==2,{
          builder:this.popupBuilder('这是第二步提示'),
          placement:Placement.Bottom,
          backgroundBlurStyle:BlurStyle.NONE,
          autoCancel:false,
          popupColor:Color.White,
          onWillDismiss:false
        })
      Text('第三步').fontSize(20).fontColor(Color.Black).position(componentGlobalPosition('text3')).backgroundColor(Color.White).padding(5).borderRadius(10)
        .visibility(this.guideStep==3?Visibility.Visible:Visibility.Hidden)
        .bindPopup(this.guideStep==3,{
          builder:this.popupBuilder('这是第三步提示'),
          placement:Placement.Bottom,
          backgroundBlurStyle:BlurStyle.NONE,
          autoCancel:false,
          popupColor:Color.White,
          onWillDismiss:false
        })
      Text(this.tabs[2]).fontSize(16).fontColor(Color.Black).position(componentGlobalPosition(this.tabs[2])).backgroundColor(Color.White).padding(5).borderRadius(10)
        .visibility(this.guideStep==4?Visibility.Visible:Visibility.Hidden)
        .bindPopup(this.guideStep==4,{
          builder:this.popupBuilder('这是第tabs提示'+this.tabs[2]),
          placement:Placement.Bottom,
          backgroundBlurStyle:BlurStyle.NONE,
          autoCancel:false,
          popupColor:Color.White,
          onWillDismiss:false
        })
    }.width('100%').height('100%').backgroundColor('#b2000000')
  }
  @Builder tabBuilder(name: string) {
    Column() {
      Text(name)
        .fontSize(16)
        .lineHeight(22)
        .margin({ top: 17, bottom: 7 }).id(name)
    }
  }
  onPageShow(): void {
    //处理home键popup被关闭
    this.guideStep=this.currentGuideStep
  }
  onPageHide(): void {
    this.guideStep=0
  }
  build() {
    Stack({alignContent:Alignment.TopStart}){
      Column(){
        RelativeContainer(){
          Text('第一步').fontSize(20).fontColor(Color.Black).id('text1')
          Text('第二步').fontSize(20).fontColor(Color.Black).alignRules(AlignRules.centerInParent).id('text2')
          Text('第三步').fontSize(20).fontColor(Color.Black).alignRules(AlignRules.alignParentRightBottom).id('text3')
        }.width('100%').height('50%')

        Tabs(){
          ForEach(this.tabs,(item: string, index:number)=>{
            TabContent() {

            }.tabBar(this.tabBuilder(item))
          })
        }.height('auto')

        Button('修改主试图,弹出引导提示').onClick(()=>{
          this.mainMargin={top:40,left:40,right:40}
          setTimeout(()=>{
            this.guideStep=1
            this.showGuilde=true
          },200)

        })
      }.onSizeChange(()=>{
        //限制是否只显示一次
        this.guideStep=1
        this.showGuilde=true
      })
      .padding(this.mainMargin)
      //指引蒙版
      if (this.showGuilde&&this.guideStep<5){
        this.showGuildComment()
      }

    }.width('100%').height('100%')
  }
}

//根据组件id获取相对于屏幕左上角的坐标
export function componentGlobalPosition(id: string): Position {
  if (!id) {
    return {x:0,y:0};
  }
  let strJson = getInspectorByKey(id);
  if (strJson) {
    let obj: Record<string, Object> = JSON.parse(strJson);
    let componentRectInfo: Record<string, Object> = JSON.parse('[' + obj.$rect + ']');
    let originLeft: number = JSON.parse('[' + componentRectInfo[0] + ']')[0];
    let originTop: number = JSON.parse('[' + componentRectInfo[0] + ']')[1];
    //获取的坐标是相当于屏幕左上角的 所以没有设置沉浸式要减去状态栏的高度
    if (!WindowUtils.isFullScreen()) {
      originTop = originTop - WindowUtils.getStatusHeight()
    }
    let position : Position = {
      x:px2vp(originLeft),
      y:px2vp(originTop)
    }
    return position;
  } else {
    return {x:0,y:0};
  }
}

注意:
getInspectorByKey是个耗时操作,需要布局完全展示之后才能获取到正确的位置。因此可以在onPageShow中延时获取,或者在主布局中onSizeChange中获取。
三方库实现原理:
通过绑定提示组件的id获取高亮区域和高亮坐标,使用Canvas绘制蒙版和高亮区域,按照添加的组件顺序,依次绘制高亮部分和引导内容,详情可以查看一下三方库源码。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
标签
1
收藏
回复
举报
回复
    相关推荐