IT评测·应用市场-qidao123.com
标题:
鸿蒙HarmonyOS 5.0开发实战:悬浮工具箱实现案例
[打印本页]
作者:
卖不甜枣
时间:
2025-3-8 22:44
标题:
鸿蒙HarmonyOS 5.0开发实战:悬浮工具箱实现案例
往期鸿蒙全套实战文章必看:(文中附带鸿蒙全栈学习资料)
鸿蒙开发核心知识点,看这篇文章就够了
最新版!鸿蒙HarmonyOS Next应用开发实战学习路线
鸿蒙HarmonyOS NEXT开发技术最全学习路线指南
鸿蒙应用开发实战项目,看这一篇文章就够了(部分项目附源码)
悬浮工具箱
介绍
本示例介绍利用zIndex、gesture等接口实现悬浮工具箱结果
结果预览图
利用说明
1.点击悬浮球,工具栏动效睁开/关闭
2.拖拽悬浮球,悬浮球跟随手势滑动
3.长按悬浮球,禁用/启用悬浮球,不再响应/再次响应悬浮球自己的点击事件
4.点击屏幕,切换横竖屏,悬浮球根据位置等效切换
下载安装
1.模块oh-package.json5文件中引入依赖
"dependencies": {
"@ohos-cases/toolbox": "har包地址"
}
复制代码
2.ets文件import自界说视图实现Tab结果组件
import { FloatingWindow } from '@ohos-cases/toolbox'
复制代码
快速利用
本节重要介绍了如何快速上手利用工具箱组件,包罗构建工具箱组件以及常见自界说参数的初始化。
1.构建工具箱组件
在代码符合的位置利用FloatingWindow组件并传入对应的参数,后续将分别介绍对应参数的初始化。
/**
* 构建工具箱
*
* content: 工具箱内容
* floatBall: 悬浮球样式
* toolBoxAttribute: 工具箱属性
*/
FloatingWindow({
toolList: this.toolList,
floatBall: this.toolTouch,
clickListener: {
onAction: (event: GestureEvent) => {
animateTo({
duration: 200
}, () => {
this.animationAttribute.visible = this.animationAttribute.visible === Visibility.Visible ? Visibility.Hidden : Visibility.Visible;
})
}
},
longClickListener: {
onAction: (event: GestureEvent, isDisable: boolean) => {
this.animationAttribute.visible = Visibility.Hidden;
},
onActionEnd: (event: GestureEvent, isDisable: boolean) => {},
onActionCancel: (isDisable: boolean) => {}
}
})
.height("250px")
.width("250px")
复制代码
2.工具项UI创建
构建一个工具项的UI结果,此中动效参数以及一些须要的静态参数通过ToolInterface接口传入。
@Builder
function tool($$: ToolInterface) {
Image(($$.params as ImgParams).imgRes)
.height(40)
.width(40)
.objectFit(ImageFit.Fill)
.visibility(($$.animation as VisibleAnimation).visible)
.onClick(() => {
promptAction.showToast({
message: '点击逻辑自行实现',
duration: 2000
})
})
}
复制代码
3.动效属性预备
通过实现一个继承于CustomAnimation的类,在该类中集成一些必要动态厘革的属性参数,此中继承类必须用@Observed修饰。本例中重要集成了一个可见属性visible,通过visible改变工具项的显隐情况。
@Observed
export class VisibleAnimation extends CustomAnimation{
// 工具项可见属性
private visible_: Visibility;
constructor(visible: Visibility = Visibility.Hidden) {
super();
this.visible_ = visible;
}
get visible(): Visibility {
return this.visible_;
}
set visible(visible: Visibility) {
this.visible_ = visible;
}
}
复制代码
4.工具项输入参数预备
预备工具项必要传入的须要参数。通过实现一个继承CustomParams的类,在该类中集成必要传入工具项的须要属性。本例中重要集成了图片资源属性imgRes,通过传入Image组件表现对应图片实现条件渲染。
export class ImgParams extends CustomParams {
// 图片资源
private imgRes_: PixelMap | ResourceStr | DrawableDescriptor;
constructor(imgRes: PixelMap | ResourceStr | DrawableDescriptor) {
super();
this.imgRes_ = imgRes;
}
get imgRes(): PixelMap | ResourceStr | DrawableDescriptor {
return this.imgRes_;
}
}
复制代码
5.构建一个工具项
新建一个CustomTool类,向此中传入三个参数——工具项UI、工具项相对于悬浮球的偏移以及属性集AttributeSet。
this.toolList[0] = new CustomTool(wrapBuilder(tool), {x: 60, y: 40}, new AttributeSet(this.animationAttribute, new ImgParams($r("app.media.AI_circle_viewfinder"))));
复制代码
5.悬浮球初始化
本小节重要介绍了如何绘制悬浮球样式。本示例中重要实现了两个button嵌套来实现悬浮球样式的绘制,此中必要传入一个ToolTouchInterface参数,内部包含了悬浮球是否禁用的属性。
@Builder
toolTouch($$: ToolTouchInterface) {
Button(){
Button()
.height(CommonConstants.EIGHTY_PERCENT)
.width(CommonConstants.EIGHTY_PERCENT)
.backgroundColor($$.isDisable ? Color.Red : Color.Gray)
.opacity(0.5)
}
.height(CommonConstants.FULL_PERCENT)
.width(CommonConstants.FULL_PERCENT)
.backgroundColor($$.isDisable ? 0xFFA28F : 0xD3D3D3)
.opacity(0.5)
}
复制代码
属性(接口)说明
FloatingWindow组件属性
属性类型释义默认值toolListCustomTool[]工具箱UI-floatBall() => void悬浮球UI-toolBoxAttributeToolBoxAttribute工具箱属性-thresholdnumber or string悬浮球开始吸边的间隔阈值(以手机宽度为基准)18%levelnumber悬浮球的堆叠优先级Number.MAX_VALUEclickListenerClickListener悬浮球点击事件监听器-longClickListenerLongClickListener悬浮球长按事件监听器-dragListenerDragListener悬浮球拖拽事件监听器- CustomTool类属性
属性类型释义默认值builderWrappedBuilder<[ToolInterface]>工具项UI-offsetOffset工具项相对于悬浮窗的偏移-attributeSetAttributeSet工具项属性集undefined Offset属性
属性类型释义默认值xnumber横坐标偏移-ynumber总坐标偏移- AttributeSet类属性
属性类型释义默认值animationCustomAnimation动效参数集(用于UI修改的参数)undefinedparamsCustomParams静态参数集(用于工具项须要的参数)undefined ClickListener属性
属性类型释义默认值onAction(event: GestureEvent) => void点击事件响应- LongClickListener属性
属性类型释义默认值onAction(event: GestureEvent, isDisable: boolean) => void长按开始回调-onActionEnd(event: GestureEvent, isDisable: boolean) => void长按结束回调-onActionCancel(isDisable: boolean) => void长按取消回调- DragListener属性
属性类型释义默认值onActionStart(event: GestureEvent) => void拖拽开始回调-onActionUpdate(event: GestureEvent) => void拖拽过程回调-onActionEnd(event: GestureEvent) => void拖拽结束回调- ToolTouchInterface属性
属性类型释义默认值isDisableboolean是否禁用工具箱- ToolInterface属性
属性类型释义默认值paramsCustomParams静态参数undefinedanimationCustomAnimation动效参数undefined
实现思路
本案例重要分为两部分内容:一部分是悬浮球手势交互实现;一部分是悬浮球横竖屏切换位置等效变更实现。
1.悬浮球手势交互
悬浮球手势交互重要分为3个部分:1.单击睁开/收回工具栏;2.长按启用/禁用工具栏;3.工具栏跟手滑动且具有吸边结果。对于三种差别的手势事件,本案例通过利用gesture接口以及GestureGroup集成三种差别的手势,并通过设置集成模式为GestureMode.Exclusive使手势之间互斥。
Column() {
this.floatBall({ isDisable: this.isDisable });
}
.gesture(
GestureGroup(GestureMode.Exclusive,
...
)
)
复制代码
1.1 单击睁开/收回工具栏
单击睁开/收回工具栏重要是通过TapGesture手势事件实现,并在clickListener的响应事件中通过切换状态变量visible实现工具栏显隐切换。
TapGesture()
.onAction((event: GestureEvent) => {
console.log(`TapGesture`)
this.clickListener.onAction(event);
})
clickListener: {
onAction: (event: GestureEvent) => {
animateTo({
duration: 200
}, () => {
this.animationAttribute.visible = this.animationAttribute.visible === Visibility.Visible ? Visibility.Hidden : Visibility.Visible;
})
}
}
复制代码
1.2 长按启用/禁用工具栏
长按启用/禁用工具栏重要是通过LongPressGesture手势事件实现,在响应事件时通过修改状态变量isDisable实现工具栏禁用/启用逻辑并实行对应的UI厘革逻辑。
LongPressGesture()
.onAction((event: GestureEvent) => {
console.log(`LongPressGesture Start`)
// TODO: 工具箱禁用逻辑
this.isDisable = !this.isDisable;
vibrator.startVibration({
type: 'preset',
effectId: 'haptic.clock.timer',
count: 1,
}, {
id: 0,
usage: 'alarm'
}, (error: BusinessError) => {
})
this.longClickListener.onAction(event, this.isDisable);
})
.onActionEnd((event: GestureEvent) => {
console.log(`LongPressGesture End`);
this.longClickListener.onActionEnd(event, this.isDisable);
})
.onActionCancel(() => {
console.log(`LongPressGesture Cancel`)
this.longClickListener.onActionCancel(this.isDisable);
})
longClickListener: {
onAction: (event: GestureEvent, isDisable: boolean) => {
this.animationAttribute.visible = Visibility.Hidden;
},
onActionEnd: (event: GestureEvent, isDisable: boolean) => {},
onActionCancel: (isDisable: boolean) => {}
}
复制代码
1.3 工具栏跟手滑动且具有吸边结果
工具栏跟手滑动通过PanGesture手势实现,在事件响应的开始阶段,初始化偏移参数;然后,在事件响应阶段,通过求取当前偏移量与上一次偏移量之间的差值实现悬浮球的滑动;最终在事件响应结束阶段,通过计算悬浮球与屏幕边缘的间隔来实现悬浮球的吸附结果。
PanGesture()
.onActionStart((event: GestureEvent) => {
this.offsetX_ = 0;
this.offsetY_ = 0;
})
.onActionUpdate((event: GestureEvent) => {
// 保证悬浮球保持在屏幕内
let curX = Math.max(this.offsetX! + event.offsetX - this.offsetX_, 0);
let curY = Math.max(this.offsetY! + event.offsetY - this.offsetY_, 0);
curX = Math.min(curX, this.screenW - this.cW);
curY = Math.min(curY, this.screenH - this.cH);
this.offsetX_ += curX - this.offsetX!;
this.offsetY_ += curY - this.offsetY!;
this.offsetX = curX;
this.offsetY = curY;
})
.onActionEnd((event: GestureEvent) => {
let left: number = this.offsetX!;
let leftMargin: number = left;
let rightMargin: number = this.screenW - leftMargin - this.cW;
// 更新工具栏展开方向
this.unfoldDirection = leftMargin <= rightMargin ? Direction.RIGHT : Direction.LEFT;
// 吸附效果实现
this.closeToBorder(left, left + this.cW, 0, this.screenW, this.realThreshold);
})
复制代码
2. 悬浮球横竖屏切换位置等效变更
本节重要介绍了如何实现横竖屏切换时悬浮球位置等效变更。为了能够判断是否发生了横竖屏切换,本案例重要通过onAreaChange接口来进行监听,这是因为屏幕发生横竖屏变更的时间,悬浮球位置必然会发生厘革。然后重要是实现悬浮球的位置等效变更,这一点重要是通过判断悬浮球与左右屏之间的间隔,来判断是利用左边距还是右边距实行百分比等效转换(上下边距转换也是一样)。
Column() {
this.floatBall({disable: this.disable});
}
.onAreaChange((oldValue: Area, newValue: Area) => {
// console.log(`onAreaChange`)
this.cW = Number.parseFloat(newValue.width.toString());
this.cH = Number.parseFloat(newValue.height.toString());
let left: number = 0;
let right: number = 0;
if (this.offsetX === undefined || this.offsetY === undefined) {
this.offsetX =
newValue.globalPosition.x === undefined ? 0 : Number.parseFloat(newValue.globalPosition.x.toString());
this.offsetY =
newValue.globalPosition.y === undefined ? 0 : Number.parseFloat(newValue.globalPosition.y.toString());
left = this.offsetX;
right = this.screenW - left - this.cW;
} else if ((1 & display.getDefaultDisplaySync().orientation) !== this.screenOrientation) { // 横竖屏切换
this.screenOrientation ^= 1;
// console.log(`${this.offsetX}, ${this.offsetY}, ${this.screenW}, ${this.screenH}`)
left = this.offsetX;
right = this.screenW - left - this.cW;
let top: number = this.offsetY;
let bottom: number = this.screenH - top - this.cH;
this.unfoldDirection = left <= right ? Direction.RIGHT : Direction.LEFT;
// console.log(`onAreaChange: ${(left / this.screenW)}`)
// console.log(`onAreaChange, ${top / this.screenH}`)
let newScreenW = this.screenOrientation === 0 ? this.initialW : this.initialH;
let newScreenH = this.screenOrientation === 0 ? (this.initialH - this.avoidSysHeight - this.avoidNavHeight) : (this.initialW - this.avoidNavHeight);
// TODO: 知识点: 通过悬浮球与左右屏之间的大小比例,来判断是使用左边距百分比还是右边距百分比实现等效转换
this.offsetX = left <= right ? (left / this.screenW * newScreenW) : (newScreenW - (right / this.screenW) * newScreenW - this.cW);
// TODO: 知识点: 通过悬浮球与上下屏之间的大小比例,来判断是使用上边距百分比还是下边距百分比实现等效转换
this.offsetY = top <= bottom ? (top / this.screenH * newScreenH) : (newScreenH - (bottom / this.screenH) * newScreenH - this.cH);
this.screenH = newScreenH;
this.screenW = newScreenW;
}
this.unfoldDirection = left <= right ? Direction.RIGHT : Direction.LEFT;
// TODO: 百分比转换
this.threshold = this.threshold.toString();
if (this.threshold.includes("%")) {
this.adpThreshold = Number.parseFloat(this.threshold.replace("%", "")) / 100 * this.screenW;
return;
}
this.adpThreshold = Number.parseFloat(this.threshold.toString());
})
复制代码
高性能知识点
无
工程布局&模块类型
toolbox // har类型
|---common
| |---CommonConstants.ets // 内置常量定义
|---model
| |---AttributeSet.est // 工具项属性集
| |---ChildTool.est // 工具项组件
| |---ClickListener.est // 悬浮球点击事件监听器
| |---CustomAnimation.est // 动效参数集
| |---CustomParams.est // 静态参数集
| |---CustomTool.est // 自定义工具项
| |---DragListener.est // 悬浮球拖拽事件监听器
| |---LongClickListener.est // 悬浮球长按事件监听器
| |---Offset.ets // 偏移类
| |---ToolInterface.est // 工具项UI入参
| |---ToolTouchInterface.est // 悬浮球UI入参
|---pages
| |---ImgParams.ets // 自定义静态参数集
| |---LoadingHUD.ets // lottie动画
| |---ToolBoxView.ets // 工具箱页面
| |---VisiableAnimation.ets // 自定义动效属性集
|---utils
| |---FloatingWindow.ets // 工具箱组件
|---FeatureComponent.ets // AppRouter入口文件
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/)
Powered by Discuz! X3.4