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,一经查实,立即删除!

相关文章

C++之模板(四)

1、用模板实现单例模式 SIngleton.h #include "iostream" using namespace std;template <typename T> class Singleton { public:static T& GetInstance(){Init();return *instance_;}private:static void Init(){if (instance_ 0){instance_ new T;a…

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;按照排期根本做不完…

【JavaScript脚本宇宙】让表单数据变得简单:探索六种强大的JavaScript库

提升表单体验&#xff1a;比较流行的JavaScript验证库 前言 在现代Web开发中&#xff0c;处理表单数据是一个常见的任务。为了简化这个过程&#xff0c;开发者通常会使用一些JavaScript库来序列化和验证表单数据。在这篇文章中&#xff0c;我们将介绍六种流行的表单处理库&am…

对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;成为企业级开发语言 &…

每天一个项目管理概念之干系人

项目管理中的干系人是指那些能够影响项目结果或者受项目结果影响的个人、群体或组织。理解并妥善管理干系人对于项目的成功至关重要。干系人管理是项目管理中的一个关键组成部分&#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的每一位进行…

从零开始设计一款gpu from scratch

基于RISC-V核心从零开始打造一款GPU芯片是一项复杂且具有挑战性的任务&#xff0c;涉及到硬件设计、软件支持、性能优化等多个方面。以下是一个详细的步骤指南&#xff0c;帮助你从零开始设计并实现一个基于RISC-V核心的GPU芯片。 1. 定义需求和目标 1.1 应用场景 确定GPU的…

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;目的是确保…

如何衡量llm 数据集的多样性

衡量大型语言模型&#xff08;LLM&#xff09;数据集的多样性是一个复杂的问题&#xff0c;因为多样性可以从多个角度来考虑。以下是一些常用的方法和指标来评估数据集的多样性&#xff1a; 词汇多样性&#xff1a; 类型-词符比&#xff08;Type-Token Ratio, TTR&#xff09;…

群辉DSM7下ZeroTier的安装

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

功率半导体静态试验测试方法

VCES 集电极-发射极饱和电压 VCES&#xff08;Voltage Collector-Emitter Saturation&#xff0c;集电极-发射极饱和电压&#xff09;是指晶体管&#xff08;通常指双极型晶体管&#xff0c;如BJT&#xff09;在饱和工作区时集电极与发射极之间的电压。 测量VCES的过程通常如…

算法笔记(二叉树1)

leetcode144 二叉树的前序遍历 递归版本 public List<Integer> preorderTraversal(TreeNode root) {List<Integer> res new ArrayList<>();preorder(root, res);return res; }public void preorder(TreeNode root, List<Integer> res) {if (root n…

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

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