
回复
当首次安装应用时,通常会有功能引导提示的需求,本文实现一个简单的功能引导案例供参考,也可以使用第三方的插件库@ohos/high_light_guide
实现效果:
实现思路:
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绘制蒙版和高亮区域,按照添加的组件顺序,依次绘制高亮部分和引导内容,详情可以查看一下三方库源码。