HarmonyOS角落里的知识:一杯冰美式的时间 -- DragView

一、前言

在学习API9的时候就写了一个DragView,用于展示某个页面的悬浮可拖动的入口,特意丰富了许多的功能,今天分享给大家~。Demo基于API11。

二、思路

因为API本身就带有拖拽的手势,所以直接使用:PanGesture,根据拖拽返回的坐标,动态的更新DragViewposition坐标。即可实现拖拽的功能。

除了拖拽,还需要的是从停留位置,吸附到某个位置。我们使用animateTo,结合坐标值即可完成很好的吸附效果。

三、准备容器

使用.position(this.curPosition)来控制拖拽的UI位置。dragContentBuilder方便自定义内容,组件的复用。

 @State private curPosition: Position = { x: 0, y: 0 };build() {Stack() {if (this.dragContentBuilder) {this.dragContentBuilder()} else {this.defDragView()}}).position(this.curPosition).onClick(this.onClickListener)}

四、边界

一般而言,拖拽的边界肯定是当前屏幕中的,但是如果需求需要限制在某个区域,或者需要规避一些位置。所以我们准备一个边界对象,来更好的管理拖拽的边界。

 boundArea: BoundArea = new BoundArea(0, 0, px2vp(display.getDefaultDisplaySync().width), px2vp(display.getDefaultDisplaySync().height))export class BoundArea {readonly start: number = 0readonly end: number = 0readonly top: number = 0readonly bottom: number = 0readonly width: number = 0readonly height: number = 0readonly centerX: number = 0readonly centerY: number = 0​constructor(start: number, top: number, end: number, bottom: number) {this.start = startthis.top = topthis.end = endthis.bottom = bottomthis.width = this.end - this.startthis.height = this.bottom - this.topthis.centerX = this.width / 2 + this.startthis.centerY = this.height / 2 + this.top}}

boundArea默认使用了整个屏幕的坐标。

五、容器大小

因为具体的UI是从外部传入的,所以宽高不确定,需要计算。我们这里使用onAreaChange,绑定到容器上:

 .onAreaChange((oldValue: Area, newValue: Area) => {let height = newValue.height as numberlet width = newValue.width as numberif ((this.dragHeight != height || this.dragWidth != width) && (height != 0 && width != 0)) {this.dragHeight = heightthis.dragWidth = width}})

可以看到,在容器发生改变的时候,我们保存它的宽高。

六、拖拽

拖拽手势使用起来还是很简单的:

 private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });

direction决定了可以在哪个方向拖,我们显然需要所有方向。当然如果后续需要限制拖动方向,修改即可。

将拖动事件绑定到容器上:

 .gesture( // 绑定PanGesture事件,监听拖拽动作PanGesture(this.panOption).onActionStart((event: GestureEvent) => {this.changePosition(event.offsetX, event.offsetY)}).onActionUpdate((event: GestureEvent) => {this.changePosition(event.offsetX, event.offsetY)}).onActionEnd((event: GestureEvent) => {this.endPosition = this.curPositionthis.adsorbToEnd(this.endPosition.x, this.endPosition.y)}))

分别处理三个事件,onActionStartonActionUpdate事件是独立的,但是逻辑一致所以全部使用this.changePosition(event.offsetX, event.offsetY)处理。

 private changePosition(offsetX: number, offsetY: number) {let targetX = this.endPosition.x + offsetX;let targetY = this.endPosition.y + offsetY;targetX = Math.max(this.boundArea.start, Math.min(targetX, this.boundArea.end - this.dragHeight));targetY = Math.max(this.boundArea.top, Math.min(targetY, this.boundArea.bottom - this.dragWidth));this.curPosition = { x: targetX, y: targetY };}

因为存在边界,所以我们需要限制curPosition的变化,在当前拖动的坐标和边界值之间取合理的值。因为容器存在宽高,所以我们需要考虑到其宽高。

当手指抬起的时候,需要做动画吸附:

 private adsorbToEnd(startX: number, startY: number) {let targetX = 0let targetY = 0if (startX <= (this.boundArea.centerX)) {targetX = this.boundArea.start + ((this.dragMargin.left ?? 0) as number)} else {targetX = this.boundArea.end - ((this.dragMargin.right ?? 0) as number) - this.dragWidth}let newTopBound = this.boundArea.top + ((this.dragMargin.top ?? 0) as number)let newBottomBound = this.boundArea.bottom - ((this.dragMargin.bottom ?? 0) as number) - this.dragWidthif (startY <= newTopBound) {targetY = newTopBound} else if (startY >= newBottomBound) {targetY = newBottomBound} else {targetY = startY}this.startMoveAnimateTo(targetX, targetY)}​private startMoveAnimateTo(x: number, y: number) {animateTo({duration: 300,curve: Curve.Smooth, iterations: 1, playMode: PlayMode.Normal, onFinish: () => {this.endPosition = this.curPosition}}, () => {this.curPosition = { x: x, y: y }})}

startX <= (this.boundArea.centerX)用于判断在边界的位置,根据位置来决定吸附到左边还是右边。计算出吸附的位置之后,只需要使用animateTo来触发this.curPosition的更新即可。

七、初始位置

如果不能控制一开始的显示位置,对于使用者的体验非常不好,所以我们可以新增一个参数Alignment来更改初始位置:

 dragAlign: Alignment = Alignment.BottomStart

可能还要微调位置,所以再加一个margin:

 dragMargin: Margin = {}

在onAreaChange的时候进行更新:

 .onAreaChange((oldValue: Area, newValue: Area) => {//.....if (this.isNotInit) {this.initAlign()}})​private initAlign() {this.isNotInit = falselet x = 0let y = 0let topMargin: number = (this.dragMargin.top ?? 0) as numberlet bottomMargin: number = (this.dragMargin.bottom ?? 0) as numberlet startMargin: number = (this.dragMargin.left ?? 0) as numberlet endMargin: number = (this.dragMargin.right ?? 0) as numberswitch (this.dragAlign) {case Alignment.Start:x = this.boundArea.start + startMarginbreak;case Alignment.Top:y = this.boundArea.top + topMarginbreak;case Alignment.End:x = this.boundArea.end - this.dragWidth - endMarginbreak;case Alignment.Bottom:y = this.boundArea.bottom - this.dragHeight - bottomMarginbreak;case Alignment.TopStart:x = this.boundArea.start + startMarginy = this.boundArea.top + topMarginbreak;case Alignment.BottomStart:x = this.boundArea.start + startMarginy = this.boundArea.bottom - this.dragHeight - bottomMarginbreak;case Alignment.BottomEnd:x = this.boundArea.end - this.dragWidth - endMarginy = this.boundArea.bottom - this.dragHeight - bottomMarginbreak;case Alignment.Center:x = this.boundArea.centerX - this.dragWidth / 2 + startMargin - endMarginy = this.boundArea.centerY - this.dragHeight / 2 + topMargin - bottomMarginbreak;}this.curPosition = { x: x, y: y }this.endPosition = this.curPosition}

只要稍微考虑容器宽高并计算下就好了。

八、使用

非常简单

 DragView({dragAlign: Alignment.Center,dragMargin: bothway(10),dragContentBuilder:this.defDragView()})​@BuilderdefDragView() {Stack() {Text("拖我").width(50).height(50).fontSize(15)}.shadow({radius: 1.5,color: "#80000000",offsetX: 0,offsetY: 1}).padding(18).borderRadius(30).backgroundColor(Color.White).animation({ duration: 200, curve: Curve.Smooth })}

当然你想往里面塞任何东西都行~

九、总结

当然还有很多人需要跨页面的悬浮窗,这可以参考应用内消息通知,活用subWindow.moveWindowTo(0, 0);

因为我使用的是Navigation路由方案,所以放在顶层直接是跨页面的。

完整的代码:(懒得上传了,只有一个import,复制即用)

 import { display, Position } from '@kit.ArkUI';​@Preview@Componentexport struct DragView {private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });private endPosition: Position = { x: 0, y: 0 }private dragHeight: number = 0private dragWidth: number = 0private dragMargin: Margin = {}boundArea: BoundArea = new BoundArea(0, 0, px2vp(display.getDefaultDisplaySync().width), px2vp(display.getDefaultDisplaySync().height))private isNotInit: boolean = true@State private curPosition: Position = { x: 0, y: 0 };dragAlign: Alignment = Alignment.BottomStartonClickListener?: (event: ClickEvent) => void@BuilderParam dragContentBuilder: CustomBuilder​build() {Stack() {if (this.dragContentBuilder) {this.dragContentBuilder()} else {this.defDragView()}}.onAreaChange((oldValue: Area, newValue: Area) => {let height = newValue.height as numberlet width = newValue.width as numberif ((this.dragHeight != height || this.dragWidth != width) && (height != 0 && width != 0)) {this.dragHeight = heightthis.dragWidth = width}if (this.isNotInit) {this.initAlign()}}).gesture( // 绑定PanGesture事件,监听拖拽动作PanGesture(this.panOption).onActionStart((event: GestureEvent) => {this.changePosition(event.offsetX, event.offsetY)}).onActionUpdate((event: GestureEvent) => {this.changePosition(event.offsetX, event.offsetY)}).onActionEnd((event: GestureEvent) => {this.endPosition = this.curPositionthis.adsorbToEnd(this.endPosition.x, this.endPosition.y)})).position(this.curPosition).onClick(this.onClickListener)}​private adsorbToEnd(startX: number, startY: number) {let targetX = 0let targetY = 0if (startX <= (this.boundArea.centerX)) {targetX = this.boundArea.start + ((this.dragMargin.left ?? 0) as number)} else {targetX = this.boundArea.end - ((this.dragMargin.right ?? 0) as number) - this.dragWidth}let newTopBound = this.boundArea.top + ((this.dragMargin.top ?? 0) as number)let newBottomBound = this.boundArea.bottom - ((this.dragMargin.bottom ?? 0) as number) - this.dragWidthif (startY <= newTopBound) {targetY = newTopBound} else if (startY >= newBottomBound) {targetY = newBottomBound} else {targetY = startY}this.startMoveAnimateTo(targetX, targetY)}​private changePosition(offsetX: number, offsetY: number) {let targetX = this.endPosition.x + offsetX;let targetY = this.endPosition.y + offsetY;​targetX = Math.max(this.boundArea.start, Math.min(targetX, this.boundArea.end - this.dragHeight));targetY = Math.max(this.boundArea.top, Math.min(targetY, this.boundArea.bottom - this.dragWidth));​this.curPosition = { x: targetX, y: targetY };}​private startMoveAnimateTo(x: number, y: number) {animateTo({duration: 300, // 动画时长curve: Curve.Smooth, // 动画曲线iterations: 1, // 播放次数playMode: PlayMode.Normal, // 动画模式onFinish: () => {this.endPosition = this.curPosition}}, () => {this.curPosition = { x: x, y: y }})}​private initAlign() {this.isNotInit = falselet x = 0let y = 0let topMargin: number = (this.dragMargin.top ?? 0) as numberlet bottomMargin: number = (this.dragMargin.bottom ?? 0) as numberlet startMargin: number = (this.dragMargin.left ?? 0) as numberlet endMargin: number = (this.dragMargin.right ?? 0) as numberswitch (this.dragAlign) {case Alignment.Start:x = this.boundArea.start + startMarginbreak;case Alignment.Top:y = this.boundArea.top + topMarginbreak;case Alignment.End:x = this.boundArea.end - this.dragWidth - endMarginbreak;case Alignment.Bottom:y = this.boundArea.bottom - this.dragHeight - bottomMarginbreak;case Alignment.TopStart:x = this.boundArea.start + startMarginy = this.boundArea.top + topMarginbreak;case Alignment.BottomStart:x = this.boundArea.start + startMarginy = this.boundArea.bottom - this.dragHeight - bottomMarginbreak;case Alignment.BottomEnd:x = this.boundArea.end - this.dragWidth - endMarginy = this.boundArea.bottom - this.dragHeight - bottomMarginbreak;case Alignment.Center:x = this.boundArea.centerX - this.dragWidth / 2 + startMargin - endMarginy = this.boundArea.centerY - this.dragHeight / 2 + topMargin - bottomMarginbreak;}​this.curPosition = { x: x, y: y }this.endPosition = this.curPosition}​@BuilderdefDragView() {Stack().width(100).height(100).backgroundColor(Color.Orange)}}​export class BoundArea {readonly start: number = 0readonly end: number = 0readonly top: number = 0readonly bottom: number = 0readonly width: number = 0readonly height: number = 0readonly centerX: number = 0readonly centerY: number = 0​constructor(start: number, top: number, end: number, bottom: number) {this.start = startthis.top = topthis.end = endthis.bottom = bottomthis.width = this.end - this.startthis.height = this.bottom - this.topthis.centerX = this.width / 2 + this.startthis.centerY = this.height / 2 + this.top}}



最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

鸿蒙HarmonyOS Next全套学习资料←点击领取!(安全链接,放心点击

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

鸿蒙(HarmonyOS NEXT)最新学习路线

有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

HarmonyOS Next 最新全套视频教程

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

《鸿蒙开发基础》

  • ArkTS语言
  • 安装DevEco Studio
  • 运用你的第一个ArkTS应用
  • ArkUI声明式UI开发
  • .……

《鸿蒙开发进阶》

  • Stage模型入门
  • 网络管理
  • 数据管理
  • 电话服务
  • 分布式应用开发
  • 通知与窗口管理
  • 多媒体技术
  • 安全技能
  • 任务管理
  • WebGL
  • 国际化开发
  • 应用测试
  • DFX面向未来设计
  • 鸿蒙系统移植和裁剪定制
  • ……

《鸿蒙进阶实战》

  • ArkTS实践
  • UIAbility应用
  • 网络案例
  • ……

大厂面试必问面试题

鸿蒙南向开发技术

鸿蒙APP开发必备

鸿蒙生态应用开发白皮书V2.0PDF



获取以上完整鸿蒙HarmonyOS学习资料,请点击→

总结
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/854971.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FUANC机器人教程:速度倍率级数与倍率增量设定方法

目录 机器人速度倍率介绍 机器人运行速度倍率切换操作 快速切换速度倍率有效与无效设定 速度倍率级数与增量自定义 速度倍率级数与增量自定义举例 机器人速度倍率介绍 在机器人手动或自动运行时都离不开速度设定&#xff0c;机器人的运行速度由多个要素共同决定&#xff…

拍照翻译软件哪个好用?打破语言壁垒就靠这5款~

还不清楚拍照翻译成中文的软件有哪些的朋友可有福了&#xff01; 今天便一次性给大家测评5款市面上热门且备受好评的拍照翻译工具~想必总有一款能够满足你的翻译需求、一举带你打破语言壁垒~ **稳定翻译的电脑软件** >>全能翻译官 翻译准确率&#xff1a;☆☆☆☆ 译…

corepack管理包管理器;nvm管理node版本;nrm管理npm源地址

corepack corepack 管理"包管理器"&#xff0c;包括 yarn 和 pnpm。corepack 并不能管理 npm。 corepack 是 nodejs 提供的功能&#xff0c;安装 nodejs 时 corepack 就一起安装了。它还是实验性功能&#xff0c;默认是关闭的&#xff0c;具体介绍看官方文档。 注…

产品经理经验分享:电商类项目开发需要了解常用的电商API接口

今天主要分享产品经理设计电商类应用需要了解的常用API&#xff1f; 为什么产品经理需要了解常用电商API接口呢&#xff1f; 1.开需求会&#xff0c;提了新的需求&#xff0c;开发说&#xff0c;你这个需求太复杂&#xff0c;光接口就有20几个&#xff0c;按照排期根本做不完…

对yoloV8进行标签过滤来实现行人检测

前言 上一章我们介绍的通过迁移学习&#xff0c;在新的行人数据集上使用已经学习到的特征和权重&#xff0c;从而更快地实现行人检测任务。模型就会调整其参数以适应新的数据集&#xff0c;以提高对行人的识别性能。接下来介绍一种更快更便捷的方法&#xff0c;依旧是基于yolo…

ES6(ECMAScript 6.0) 新特性

1 ES6 基本介绍 &#xff08;1&#xff09;ECMAScript 6.0(简称 ES6)是 JavaScript 语言的下一代标准&#xff0c; 2015 年 6 月发布。 &#xff08;2&#xff09;ES6 设计目标&#xff1a;达到 JavaScript 语言可以用来编写复杂的大型程序&#xff0c;成为企业级开发语言 &…

Python机器学习完整流程:从数据清洗到推理落地

目录 一、引言 二、数据清洗 数据加载与初步探索 缺失值处理 异常值处理 特征编码与转换 数据集划分 三、模型训练 四、模型文件生成 五、模型部署与推理落地 六、总结 一、引言 在当今数据驱动的时代&#xff0c;机器学习已成为解决复杂问题的有力工具。而…

预埋螺栓抗滑移系数检测 内六角螺栓扭矩系数检测

螺栓检测范围&#xff1a;螺栓&#xff0c;高强螺栓&#xff0c;地脚螺栓&#xff0c;不锈钢螺栓&#xff0c;六角头螺栓&#xff0c;管片螺栓&#xff0c;膨胀螺栓&#xff0c;化学螺栓&#xff0c;镀锌螺栓&#xff0c;植筋螺栓&#xff0c;普通螺栓&#xff0c;钢结构螺栓&a…

Golang免杀-编码加密-Xor(GG)

go语言环境搭建 Golang学习日志 ━━ 下载及安装_golang下载-CSDN博客 go run xxx.go go build xxx.go 首先,cs.msf生成比特流数据. 放入xor,py脚本中进行xor加密. xor.py def xor(shellcode, key):new_shellcode ""key_len len(key)# 对shellcode的每一位进行…

JAVA台球助教台球教练多端系统源码支持微信小程序+微信公众号+H5+APP

&#x1f3b1;台球助教系统&#xff1a;你的私人教练在线等你&#x1f3af; 功能介绍 球厅端&#xff1a;球厅认证、教练人数、教练的位置记录、助教申请、我的项目、签到记录、我的钱包、数据统计 教练端&#xff1a;我的页面&#xff0c;数据统计、订单详情、保证金、实名…

CP测试是什么 及名词解释

芯片中的CP一般指的是CP测试&#xff0c;也就是晶圆测试&#xff08;Chip Probing&#xff09;。 一、CP测试是什么 CP测试在整个芯片制作流程中处于晶圆制造和封装之间&#xff0c;测试对象是针对整片晶圆&#xff08;Wafer&#xff09;中的每一个Die&#xff0c;目的是确保…

群辉DSM7下ZeroTier的安装

目录 一、起因 二、具体操作 1、添加组件源: 2、安装套件 3、开启ssh 4、连接ssh执行修补 5、手工启动ZeroTier 6、使用终端命令加入网络 7、审核通过该节点的加入 三、测试链接 1、PC端测试 2、手机APP测试 ZeroTier是个内网穿透的远程组网系统,它可以将全世界的终…

【电子数据取证】如何快速在CSV中找到涉案手机号码

文章关键词&#xff1a;电子数据取证、聊天记录恢复、数据恢复、手机取证、介质取证 一、前言 在最近的取证工作中&#xff0c;我们遇到很多需要从大量的聊天记录数据中提取特定的信息&#xff0c;例如手机号码&#xff0c;银行号码&#xff0c;交易码。由于数据通常以数据库…

Linux系统下多网卡多网关设置

场景一&#xff1a; 主机AB得网卡1和网卡2都分别划分在VLAN1和VLAN2中&#xff0c;主机C在VLAN3中&#xff0c;VLAN1&#xff0c;2&#xff0c;3在三层交换设备上配置好网关192.168.1.1 192.168.2.1 192.168.3.1&#xff0c;并开启三层交换功能。 主机A的两块网卡分别IP为192…

Sectigo OV通配符SSL证书多少钱?

在网络安全领域&#xff0c;SSL数字证书起着至关重要的作用&#xff0c;尤其是在保护网站和用户信息方面。而Sectigo OV通配符证书是一种常用的数字证书之一&#xff0c;它能够为同一域名下的多个子域名提供保护&#xff0c;还能够通过企业验证来增强安全性。那么&#xff0c;对…

边缘检测(一)-灰度图像边缘检测方法

灰度图像边缘检测是数字图像处理与机器视觉中经常遇到的一个问题&#xff0c;边缘检测是否连续、光滑是判断检测方法优劣的一个重要标准&#xff0c;下面通过一个实例提供灰度图像边缘检测方法&#xff0c;该方法对其他图像检测也具有一定的参考价值。 首先&#xff0c;读入一幅…

inpaint下载安装2024-inpaint软件安装包下载v5.0.6官网最新版附加详细安装步骤

Inpaint软件最新版是一款功能强大的图片去水印软件&#xff0c;这款软件拥有强大的智能算法&#xff0c;能够根据照片的背景为用户去除照片中的各种水印&#xff0c;并修补好去除水印后的图片。并且软件操作简单、界面清爽&#xff0c;即使是修图新手也能够轻松上手&#xff0c…

雨水情监测系统解决方案

一、系统介绍 水库雨水情自动测报系统辅助水利管理部门实现水库雨水情信息“全要素、全量程、全覆盖”自动测报。系统具备水库水位、雨量、现场图像/视频等水文信息采集、传输、处理及预警广播等功能&#xff0c;有效提升了雨水情信息的时效性和准确度&#xff0c;为保障水库安…

国内docker镜像加速

自己注册一个阿里云或者华为云的账户&#xff0c;搜索镜像 点击开通&#xff0c;再点击镜像加速器&#xff0c;可以看到自己的加速器地址&#xff0c;然后替换就可以了。再去pull即可成功&#xff0c;但是响应还是要慢一点

创建应用程序

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 使用wxPython之前&#xff0c;先来了解两个基础对象&#xff1a;应用程序对象和顶级窗口。 应用程序对象管理主事件循环&#xff0c;主事件循环是wx…