当前位置:   article > 正文

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

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

一、前言

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

二、思路

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

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

三、准备容器

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

  1.  @State private curPosition: Position = { x: 0, y: 0 };
  2.  build() {
  3.      Stack() {
  4.          if (this.dragContentBuilder) {
  5.              this.dragContentBuilder()
  6.         } else {
  7.              this.defDragView()
  8.         }
  9.     }
  10.     )
  11.     .position(this.curPosition)
  12.     .onClick(this.onClickListener)
  13.  }

四、边界

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

  1.  boundArea: BoundArea = new BoundArea(0, 0, px2vp(display.getDefaultDisplaySync()
  2.         .width), px2vp(display.getDefaultDisplaySync().height))
  3.  export class BoundArea {
  4.      readonly start: number = 0
  5.      readonly end: number = 0
  6.      readonly top: number = 0
  7.      readonly bottom: number = 0
  8.      readonly width: number = 0
  9.      readonly height: number = 0
  10.      readonly centerX: number = 0
  11.      readonly centerY: number = 0
  12.  ​
  13.      constructor(start: number, top: number, end: number, bottom: number) {
  14.          this.start = start
  15.          this.top = top
  16.          this.end = end
  17.          this.bottom = bottom
  18.          this.width = this.end - this.start
  19.          this.height = this.bottom - this.top
  20.          this.centerX = this.width / 2 + this.start
  21.          this.centerY = this.height / 2 + this.top
  22.     }
  23.  }

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

五、容器大小

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

  1.  .onAreaChange((oldValue: Area, newValue: Area) => {
  2.      let height = newValue.height as number
  3.      let width = newValue.width as number
  4.      if ((this.dragHeight != height || this.dragWidth != width) && (height != 0 && width != 0)) {
  5.          this.dragHeight = height
  6.          this.dragWidth = width
  7.     }
  8.  })

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

六、拖拽

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

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

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

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

  1.  .gesture( // 绑定PanGesture事件,监听拖拽动作
  2.     PanGesture(this.panOption)
  3.         .onActionStart((event: GestureEvent) => {
  4.             this.changePosition(event.offsetX, event.offsetY)
  5.         })
  6.         .onActionUpdate((event: GestureEvent) => {
  7.             this.changePosition(event.offsetX, event.offsetY)
  8.         })
  9.         .onActionEnd((event: GestureEvent) => {
  10.             this.endPosition = this.curPosition
  11.             this.adsorbToEnd(this.endPosition.x, this.endPosition.y)
  12.         })
  13.  )

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

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

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

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

  1.  private adsorbToEnd(startX: number, startY: number) {
  2.      let targetX = 0
  3.      let targetY = 0
  4.      if (startX <= (this.boundArea.centerX)) {
  5.          targetX = this.boundArea.start + ((this.dragMargin.left ?? 0) as number)
  6.     } else {
  7.          targetX = this.boundArea.end - ((this.dragMargin.right ?? 0) as number) - this.dragWidth
  8.     }
  9.      let newTopBound = this.boundArea.top + ((this.dragMargin.top ?? 0) as number)
  10.      let newBottomBound = this.boundArea.bottom - ((this.dragMargin.bottom ?? 0) as number) - this.dragWidth
  11.      if (startY <= newTopBound) {
  12.          targetY = newTopBound
  13.     } else if (startY >= newBottomBound) {
  14.          targetY = newBottomBound
  15.     } else {
  16.          targetY = startY
  17.     }
  18.      this.startMoveAnimateTo(targetX, targetY)
  19.  }
  20.  ​
  21.  private startMoveAnimateTo(x: number, y: number) {
  22.      animateTo({
  23.          duration: 300,
  24.          curve: Curve.Smooth,
  25.          iterations: 1,
  26.          playMode: PlayMode.Normal,
  27.          onFinish: () => {
  28.              this.endPosition = this.curPosition
  29.         }
  30.     }, () => {
  31.          this.curPosition = { x: x, y: y }
  32.     })
  33.  }

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

七、初始位置

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

 dragAlign: Alignment = Alignment.BottomStart

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

 dragMargin: Margin = {}

在onAreaChange的时候进行更新:

  1.  .onAreaChange((oldValue: Area, newValue: Area) => {
  2.      //.....
  3.      if (this.isNotInit) {
  4.          this.initAlign()
  5.     }
  6.  })
  7.  ​
  8.  private initAlign() {
  9.      this.isNotInit = false
  10.      let x = 0
  11.      let y = 0
  12.      let topMargin: number = (this.dragMargin.top ?? 0) as number
  13.      let bottomMargin: number = (this.dragMargin.bottom ?? 0) as number
  14.      let startMargin: number = (this.dragMargin.left ?? 0) as number
  15.      let endMargin: number = (this.dragMargin.right ?? 0) as number
  16.      switch (this.dragAlign) {
  17.          case Alignment.Start:
  18.              x = this.boundArea.start + startMargin
  19.              break;
  20.          case Alignment.Top:
  21.              y = this.boundArea.top + topMargin
  22.              break;
  23.          case Alignment.End:
  24.              x = this.boundArea.end - this.dragWidth - endMargin
  25.              break;
  26.          case Alignment.Bottom:
  27.              y = this.boundArea.bottom - this.dragHeight - bottomMargin
  28.              break;
  29.          case Alignment.TopStart:
  30.              x = this.boundArea.start + startMargin
  31.              y = this.boundArea.top + topMargin
  32.              break;
  33.          case Alignment.BottomStart:
  34.              x = this.boundArea.start + startMargin
  35.              y = this.boundArea.bottom - this.dragHeight - bottomMargin
  36.              break;
  37.          case Alignment.BottomEnd:
  38.              x = this.boundArea.end - this.dragWidth - endMargin
  39.              y = this.boundArea.bottom - this.dragHeight - bottomMargin
  40.              break;
  41.          case Alignment.Center:
  42.              x = this.boundArea.centerX - this.dragWidth / 2 + startMargin - endMargin
  43.              y = this.boundArea.centerY - this.dragHeight / 2 + topMargin - bottomMargin
  44.              break;
  45.     }
  46.      this.curPosition = { x: x, y: y }
  47.      this.endPosition = this.curPosition
  48.  }

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

八、使用

非常简单

  1.  DragView({
  2.      dragAlign: Alignment.Center,
  3.      dragMargin: bothway(10),
  4.      dragContentBuilder:this.defDragView()
  5.  })
  6.  ​
  7.  @Builder
  8.  defDragView() {
  9.      Stack() {
  10.          Text("拖我")
  11.             .width(50)
  12.             .height(50)
  13.             .fontSize(15)
  14.     }
  15.     .shadow({
  16.          radius: 1.5,
  17.          color: "#80000000",
  18.          offsetX: 0,
  19.          offsetY: 1
  20.     })
  21.     .padding(18)
  22.     .borderRadius(30)
  23.     .backgroundColor(Color.White)
  24.     .animation({ duration: 200, curve: Curve.Smooth })
  25.  }

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

九、总结

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

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

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

  1.  import { display, Position } from '@kit.ArkUI';
  2.  ​
  3.  @Preview
  4.  @Component
  5.  export struct DragView {
  6.      private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });
  7.      private endPosition: Position = { x: 0, y: 0 }
  8.      private dragHeight: number = 0
  9.      private dragWidth: number = 0
  10.      private dragMargin: Margin = {}
  11.      boundArea: BoundArea = new BoundArea(0, 0, px2vp(display.getDefaultDisplaySync()
  12.         .width), px2vp(display.getDefaultDisplaySync().height))
  13.      private isNotInit: boolean = true
  14.      @State private curPosition: Position = { x: 0, y: 0 };
  15.      dragAlign: Alignment = Alignment.BottomStart
  16.      onClickListener?: (event: ClickEvent) => void
  17.      @BuilderParam dragContentBuilder: CustomBuilder
  18.  ​
  19.      build() {
  20.          Stack() {
  21.              if (this.dragContentBuilder) {
  22.                  this.dragContentBuilder()
  23.             } else {
  24.                  this.defDragView()
  25.             }
  26.         }
  27.         .onAreaChange((oldValue: Area, newValue: Area) => {
  28.              let height = newValue.height as number
  29.              let width = newValue.width as number
  30.              if ((this.dragHeight != height || this.dragWidth != width) && (height != 0 && width != 0)) {
  31.                  this.dragHeight = height
  32.                  this.dragWidth = width
  33.             }
  34.              if (this.isNotInit) {
  35.                  this.initAlign()
  36.             }
  37.         })
  38.         .gesture( // 绑定PanGesture事件,监听拖拽动作
  39.              PanGesture(this.panOption)
  40.                 .onActionStart((event: GestureEvent) => {
  41.                      this.changePosition(event.offsetX, event.offsetY)
  42.                 })
  43.                 .onActionUpdate((event: GestureEvent) => {
  44.                      this.changePosition(event.offsetX, event.offsetY)
  45.                 })
  46.                 .onActionEnd((event: GestureEvent) => {
  47.                      this.endPosition = this.curPosition
  48.                      this.adsorbToEnd(this.endPosition.x, this.endPosition.y)
  49.                 })
  50.         )
  51.         .position(this.curPosition)
  52.         .onClick(this.onClickListener)
  53.     }
  54.  ​
  55.      private adsorbToEnd(startX: number, startY: number) {
  56.          let targetX = 0
  57.          let targetY = 0
  58.          if (startX <= (this.boundArea.centerX)) {
  59.              targetX = this.boundArea.start + ((this.dragMargin.left ?? 0) as number)
  60.         } else {
  61.              targetX = this.boundArea.end - ((this.dragMargin.right ?? 0) as number) - this.dragWidth
  62.         }
  63.          let newTopBound = this.boundArea.top + ((this.dragMargin.top ?? 0) as number)
  64.          let newBottomBound = this.boundArea.bottom - ((this.dragMargin.bottom ?? 0) as number) - this.dragWidth
  65.          if (startY <= newTopBound) {
  66.              targetY = newTopBound
  67.         } else if (startY >= newBottomBound) {
  68.              targetY = newBottomBound
  69.         } else {
  70.              targetY = startY
  71.         }
  72.          this.startMoveAnimateTo(targetX, targetY)
  73.     }
  74.  ​
  75.      private changePosition(offsetX: number, offsetY: number) {
  76.          let targetX = this.endPosition.x + offsetX;
  77.          let targetY = this.endPosition.y + offsetY;
  78.  ​
  79.          targetX = Math.max(this.boundArea.start, Math.min(targetX, this.boundArea.end - this.dragHeight));
  80.          targetY = Math.max(this.boundArea.top, Math.min(targetY, this.boundArea.bottom - this.dragWidth));
  81.  ​
  82.          this.curPosition = { x: targetX, y: targetY };
  83.     }
  84.  ​
  85.      private startMoveAnimateTo(x: number, y: number) {
  86.          animateTo({
  87.              duration: 300, // 动画时长
  88.              curve: Curve.Smooth, // 动画曲线
  89.              iterations: 1, // 播放次数
  90.              playMode: PlayMode.Normal, // 动画模式
  91.              onFinish: () => {
  92.                  this.endPosition = this.curPosition
  93.             }
  94.         }, () => {
  95.              this.curPosition = { x: x, y: y }
  96.         })
  97.     }
  98.  ​
  99.      private initAlign() {
  100.          this.isNotInit = false
  101.          let x = 0
  102.          let y = 0
  103.          let topMargin: number = (this.dragMargin.top ?? 0) as number
  104.          let bottomMargin: number = (this.dragMargin.bottom ?? 0) as number
  105.          let startMargin: number = (this.dragMargin.left ?? 0) as number
  106.          let endMargin: number = (this.dragMargin.right ?? 0) as number
  107.          switch (this.dragAlign) {
  108.              case Alignment.Start:
  109.                  x = this.boundArea.start + startMargin
  110.                  break;
  111.              case Alignment.Top:
  112.                  y = this.boundArea.top + topMargin
  113.                  break;
  114.              case Alignment.End:
  115.                  x = this.boundArea.end - this.dragWidth - endMargin
  116.                  break;
  117.              case Alignment.Bottom:
  118.                  y = this.boundArea.bottom - this.dragHeight - bottomMargin
  119.                  break;
  120.              case Alignment.TopStart:
  121.                  x = this.boundArea.start + startMargin
  122.                  y = this.boundArea.top + topMargin
  123.                  break;
  124.              case Alignment.BottomStart:
  125.                  x = this.boundArea.start + startMargin
  126.                  y = this.boundArea.bottom - this.dragHeight - bottomMargin
  127.                  break;
  128.              case Alignment.BottomEnd:
  129.                  x = this.boundArea.end - this.dragWidth - endMargin
  130.                  y = this.boundArea.bottom - this.dragHeight - bottomMargin
  131.                  break;
  132.              case Alignment.Center:
  133.                  x = this.boundArea.centerX - this.dragWidth / 2 + startMargin - endMargin
  134.                  y = this.boundArea.centerY - this.dragHeight / 2 + topMargin - bottomMargin
  135.                  break;
  136.         }
  137.  ​
  138.          this.curPosition = { x: x, y: y }
  139.          this.endPosition = this.curPosition
  140.     }
  141.  ​
  142.      @Builder
  143.      defDragView() {
  144.          Stack()
  145.             .width(100)
  146.             .height(100)
  147.             .backgroundColor(Color.Orange)
  148.     }
  149.  }
  150.  ​
  151.  export class BoundArea {
  152.      readonly start: number = 0
  153.      readonly end: number = 0
  154.      readonly top: number = 0
  155.      readonly bottom: number = 0
  156.      readonly width: number = 0
  157.      readonly height: number = 0
  158.      readonly centerX: number = 0
  159.      readonly centerY: number = 0
  160.  ​
  161.      constructor(start: number, top: number, end: number, bottom: number) {
  162.          this.start = start
  163.          this.top = top
  164.          this.end = end
  165.          this.bottom = bottom
  166.          this.width = this.end - this.start
  167.          this.height = this.bottom - this.top
  168.          this.centerX = this.width / 2 + this.start
  169.          this.centerY = this.height / 2 + this.top
  170.     }
  171.  }



最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(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学习资料,请点击→

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/760725
推荐阅读
相关标签
  

闽ICP备14008679号