【如此之白】OpenHarmony ArkUI实现Web API Drag拖拽效果 原创 精华

如此之白
发布于 2022-2-12 18:50
浏览
5收藏

大家经常浏览网页,是不是发现鼠标按住图片拖动后,可以拖出一个虚影。

【如此之白】OpenHarmony ArkUI实现Web API Drag拖拽效果-鸿蒙开发者社区

对于WEB前端,Web API提供drag接口,很简单就可以实现一个这样的效果。
对于OpenHarmony应用开发的小伙伴们,我们也可以利用onTouch实现一个这样的效果。

拖拽效果展示

【如此之白】OpenHarmony ArkUI实现Web API Drag拖拽效果-鸿蒙开发者社区

完整代码查看:

https://gitee.com/hytyj_hamstermie/tian-open-document.git

ArkUI实现Web API Drag拖拽效果/demo/page.ets

实现思路

实现思路很简单。

我们设置一个模拟块,这个模拟块是永远叠在最上面的。

模拟块一开始是隐藏的。

当用户触摸到绑定touch事件的一个固定块后,这个模拟块将拷贝该固定块的所有样式、内容,并获取该固定块的位置。

显示模拟块,并覆盖在固定块上。

在触摸事件持续过程中,模拟块跟随手指触摸坐标进行自身的位移更新。

在触摸事件结束后,模拟块消失。

【如此之白】OpenHarmony ArkUI实现Web API Drag拖拽效果-鸿蒙开发者社区

开发环境

  • IDE: DevEco Studio 3.0beta2 Build Version 3.0.0.800
  • SDK Version: 8
  • supportSystem “standard”
  • 系统版本:OpenHarmony 3.1beta

实践

效果如图:

【如此之白】OpenHarmony ArkUI实现Web API Drag拖拽效果-鸿蒙开发者社区

定义块样式

首先,我们先定义出固定块的样式。使用@Extend装饰器,我们可以更快地复制块样式。

使用@Extend装饰器

IDE中使用@Extend装饰器会报错Duplicate function implementation.

但OpenHarmony是支持@Extend装饰器的。推测可能属于IDE问题,这里推荐使用typescript忽略。

// 单行忽略
// @ts-ignore

// 忽略全文
// @ts-nocheck

// 取消忽略全文
// @ts-check

请慎用@ts-nocheck,之前因为方便,直接使用了忽略全文,导致调试时,很多语法问题没有修改回来。

我们给所有@Extend上面标注上// @ts-ignore

  • 块样式
// @ts-ignore
@Extend(Flex) function blockStyle (opacity: number, backgroundColor: Color, width: number, height: number) {
  .width(width)
  .height(height)
  .opacity(opacity)
  .backgroundColor(backgroundColor)
}
  • 块内文字样式
// @ts-ignore
@Extend(Text) function blockTextStyle (color: Color, size: number) {
  .fontColor(color)
  .fontSize(size)
  .align(Alignment.Center)
}

注意:

  • @Extend装饰器方法是无法定义默认参数的
  • @Extend装饰器不能在struct中使用

定义固定块数据

@State装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法进行UI刷新。

//定义块组数据
@State blockInfos: any[] = [
        {
          text: "A",
          textsize: 30,
          color: "#ffffff",
          bgcolor: "#336699",
          opacity: 1
        },
        {
          text: "B",
          textsize: 30,
          color: "#ffffff",
          bgcolor: "#339961",
          opacity: 1
        },
        {
          text: "C",
          textsize: 30,
          color: "#ffffff",
          bgcolor: "#929933",
          opacity: 1
        },
        {
          text: "D",
          textsize: 30,
          color: "#ffffff",
          bgcolor: "#995033",
          opacity: 1
        }
  ]

简单块样式相关数据:

  • 文字内容
  • 文字大小
  • 文字颜色
  • 背景颜色
  • 透明度

定义渲染固定块方法

@Builder装饰器必须在struct内使用

@Builder装饰的方法可以定义默认参数

把主要传入参数放在最前:

  • 文字内容
  • 背景颜色
  • 透明度
  • touch事件方法
//可以设置默认参数
@Builder ItemBlock(
    text:string,
    bgcolor: Color,
    opacity: number,
    touchevent: any,
    textcolor: Color = Color.White,
    textsize:  number = 30,
    width: number = 90,
    height: number = 90,
  ) {
    Flex({
      alignItems: ItemAlign.Center,
      justifyContent: FlexAlign.Center
    }) {
      Text(text).blockTextStyle(textcolor, textsize)
    }
    .blockStyle(opacity, bgcolor, width, height)
    .onTouch(touchevent)
  }

定义渲染模拟块方法

定义模拟块相关状态数据

//位移坐标x
@State x: number = 0
//位移坐标y
@State y: number = 0
//是否显示
@State flayerShow: boolean = false
//模拟块宽度
@State flayerWidth: number = 0
//模拟块高度
@State flayerHeight: number = 0
//模拟块文字内容
@State flayerText: string = ""
//模拟块文字大小
@State flayerTextSize: number = 30
//模拟块文字颜色
@State flayerTextColor: Color = Color.White
//模拟块背景颜色
@State flayerBgColor: Color = Color.Black

实现

@Builder DragBlock(
    text:string = this.flayerText,
    bgcolor: Color = this.flayerBgColor,
    textcolor: Color = this.flayerTextColor,
    textsize: number = this.flayerTextSize,
    width: number = this.flayerWidth,
    height: number = this.flayerHeight,
    opacity: number = 1
  ) {
    Flex({
      alignItems: ItemAlign.Center,
      justifyContent: FlexAlign.Center
    }) {
      Text(this.flayerText).blockTextStyle(textcolor, textsize)
    }
    .blockStyle(opacity, bgcolor, width, height)
    .position({
      x: this.x,
      y: this.y
    })
  }

布局

//堆叠容器
Stack() {
    //固定块
    Flex({
        direction: FlexDirection.Column,
        alignItems: ItemAlign.Center,
        justifyContent: FlexAlign.Center
    }) {
        Grid() {
          // OpenHarmony 3.1beta已修复ForEach返回index失效问题
          ForEach(this.blockInfos, (info, index) => {
            GridItem() {
              Flex({
                direction: FlexDirection.Column,
                alignItems: ItemAlign.Center,
                justifyContent: FlexAlign.SpaceAround
              }) {
                this.ItemBlock(
                    info.text, 
                    info.bgcolor, 
                    info.opacity, 
                    (e: TouchEvent) => {
                    	//注意这里,就是实现拖拽的重点
                	})
              }
            }
          })
        }
        .columnsTemplate('1fr 1fr 1fr')
        .rowsTemplate('1fr 1fr')
        .backgroundColor("#dddddd")
        .margin(20)
        .height(300)
    }
    .backgroundColor("#f8f8f8")
    .width("100%")
    .height("100%")
    
    //模拟块,叠在最上层,通过flayerShow控制其展示
    if (this.flayerShow) {
        this.DragBlock()
    }
}
.width('100%')
.height('100%')

了解onTouch

参考HarmonyOS触摸事件:https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-universal-events-touch-0000001158261221

OpenHarmony区别

OpenHarmony对比HarmonyOS的API文档,在TouchEvent中多返回了一个target对象。

target携带了当前绑定触摸组件的基本信息,使我们实现一些能力更加便利。

TouchEvent信息示例:

{
  "timestamp":345459939477,
  "target":{
    "area":{
      "pos":{"x":0,"y":30},
      "globalPos":{"x":88.33333333333333,"y":520},
      "width":90,
      "height":90
    }
  },
  "touches":[{"type":0,"id":1,"screenX":126,"screenY":571.5,"x":37.66666793823242,"y":51.5}],
  "type":0,
  "changedTouches":[{"type":0,"id":1,"screenX":126,"screenY":571.5,"x":37.66666793823242,"y":51.5}]
}

触摸事件

名称 是否冒泡 功能描述
onTouch(callback: (event?: TouchEvent) => void) 触摸动作触发该方法调用,event参数见TouchEvent介绍。

TouchEvent对象说明

属性

属性名称 类型 描述
type TouchType 触摸事件的类型。
touches Array<TouchObject> 全部手指信息。
changedTouches Array<TouchObject> 当前发生变化的手指信息。
timestamp number 事件时间戳。
target TargetObject 触发组件的信息,仅TouchType.Down时有信息

TouchObject对象说明

属性名称 类型 描述
type TouchType 触摸事件的类型。
id number 手指唯一标识符。
screenX number 触摸点相对于设备屏幕左边沿的X坐标。
screenY number 触摸点相对于设备屏幕上边沿的Y坐标。
x number 触摸点相对于被触摸元素左边沿的X坐标。
y number 触摸点相对于被触摸元素上边沿的Y坐标。

TouchType枚举说明

名称 描述
Down 手指按下时触发。
Up 手指抬起时触发。
Move 手指按压态在屏幕上移动时触发。
Cancel 触摸事件取消时触发。

TargetObject对象说明

属性名称 类型 描述
area AreaObject 触摸到的组件区域信息

AreaObject对象说明

属性名称 类型 描述
pos 坐标对象{x,y} 未知
globalPos 坐标对象{x,y} 组件相对于设备屏幕左上边沿的坐标
width number 组件宽度信息
height number 组件高度信息

实现onTouch方法

现在就让我们来实现最重要的onTouch方法啦

一个拖拽的实现会有三个阶段:

  • 手指按下,对应TouchType.Down
  • 手指拖动,对应TouchType.Move
  • 手指松开,对应TouchType.Up
if (e.type === TouchType.Down) {
    //手指按下
} else if (e.type === TouchType.Move) {
   //手指拖动
} else if (e.type === TouchType.Up) {
   //手指松开
}

手指按下

在IDE中,获取到的坐标,组件的高宽,返回的数据类型并不是API文档中写的number而是一个Length类型。

Length是一个长度类型。

名称 类型定义 描述
Length string | number 用于描述尺寸单位,输入为number类型时,使用vp单位;输入为string类型时,需要显式指定像素单位,如’10px’,也可设置百分比字符串,如’100%'。

由于之前我们定义的数据类型为number,所以这里我们可以使用as类型断言,将当前数据类型断言为number

if (e.type === TouchType.Down) {
    //拷贝块样式
    let areainfo = e.target.area
    
    //断言数据类型
    this.flayerWidth = areainfo.width as number
    this.flayerHeight = areainfo.height as number
    
    this.flayerText = info.text
    this.flayerTextSize = info.textsize
    this.flayerTextColor = info.color
    this.flayerBgColor = info.bgcolor
    
    //获取初始坐标
    //断言数据类型
    this.x = areainfo.globalPos.x as number
    this.y = areainfo.globalPos.y as number
    
    //拷贝完成,展示模拟块
    this.flayerShow = true

    //将的对应块中的透明度数据设置为0.2
    this.blockInfos[index].opacity = 0.2
}

手指拖动

将模拟块的坐标不断更新为手指的触摸坐标。

我们希望拖动的是块的中心点,那么就要将坐标做一个偏移处理。偏移量就是:块的宽度/2,块的高度/2。

else if (e.type === TouchType.Move) {
  this.x = e.touches[0].screenX - this.flayerWidth / 2
  this.y = e.touches[0].screenY - this.flayerHeight / 2
}

手指松开

手指松开后这个拖拽操作也终止了,那么就应该让这个模拟块消失了。

else if (e.type === TouchType.Up) {
    this.flayerShow = false
    this.blockInfos[index].opacity = 1
}

这样一个简单的拖拽就完成了,打开Previewer看看效果吧。

问题

虽然在模拟器上能很好运行,但是在开发板上出现了问题:

应用运行在开发板上时,TouchEvent内的所有值,都比应得值小了1倍,在进行拖动时,会发现位置有很大偏移。

这个问题已提交issue,希望OpenHarmony能够解决吧。

issue: https://gitee.com/openharmony/docs/issues/I4TF75

另外,由于缺少GPU支持,在开发板上会很卡。

开发板上查看

想要在开发板(3516/rk3568)上查看的小伙伴,可以将touchEvent返回数值*2就可以看到正常的效果。

if (e.type === TouchType.Down) {
    let areainfo = e.target.area
    
    //返回的高宽信息*2
    this.flayerWidth = areainfo.width as number * 2
    this.flayerHeight = areainfo.height as number * 2
    
    this.flayerText = info.text
    this.flayerTextSize = info.textsize
    this.flayerTextColor = info.color
    this.flayerBgColor = info.bgcolor
    this.flayerShow = true
    
    //返回的坐标*2
    this.x = areainfo.globalPos.x as number * 2
    this.y = areainfo.globalPos.y as number * 2
    
    this.blockInfos[index].opacity = 0.2
}else if (e.type === TouchType.Move) {
  	this.x = e.touches[0].screenX * 2 - this.flayerWidth / 2
  	this.y = e.touches[0].screenY * 2 - this.flayerHeight / 2
}

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-9-7 11:47:13修改
11
收藏 5
回复
举报
1条回复
按时间正序
/
按时间倒序
甜甜爱开发
甜甜爱开发

又一个程序员女神。不错的文章,谢谢分享!

回复
2022-2-14 10:32:54
回复
    相关推荐