HarmonyOS 鸿蒙Next中在使用拖拽onDragStart时,如果拖拽元素设置了rotate的角度angle会导致拖拽开始时发生位移,该怎么解决?
HarmonyOS 鸿蒙Next中在使用拖拽onDragStart时,如果拖拽元素设置了rotate的角度angle会导致拖拽开始时发生位移,该怎么解决?
@ComponentV2
export struct Drag {
@Local dataList: Array<number> = []
@Local dragData: number | null = null
aboutToAppear(): void {
for (let index = 0; index < 10; index++) {
this.dataList.push(index)
}
}
build() {
Column() {
Text(this.dragData + '')
Grid() {
ForEach(this.dataList, (item: number)=> {
GridItem() {
Text(item+ '')
}.height(80)
.backgroundColor('#ffffff')
.visibility(item===this.dragData?Visibility.Hidden: Visibility.Visible)
.rotate({
z: 1,
angle: 2
})
.onDragStart((event?: DragEvent, extraParams?: string) => {
this.dragData = item
})
.onDragEnd((event?: DragEvent, extraParams?: string)=> {
this.dragData = null
})
.onDrop((event?: DragEvent, extraParams?: string) => {
})
})
}.columnsTemplate('1fr 1fr')
.columnsGap(12)
.rowsGap(12)
.padding(12)
.backgroundColor(Color.Orange)
}
}
}
更多关于HarmonyOS 鸿蒙Next中在使用拖拽onDragStart时,如果拖拽元素设置了rotate的角度angle会导致拖拽开始时发生位移,该怎么解决?的实战教程也可以访问 https://www.itying.com/category-93-b0.html
方案1:使用外层容器包裹(推荐)
将 rotate 应用到内层元素,而不是被拖拽的 GridItem 本身:
@ComponentV2
export struct Drag {
@Local dataList: Array<number> = []
@Local dragData: number | null = null
aboutToAppear(): void {
for (let index = 0; index < 10; index++) {
this.dataList.push(index)
}
}
build() {
Column() {
Text(this.dragData + '')
Grid() {
ForEach(this.dataList, (item: number) => {
GridItem() {
// 将 rotate 移到内层容器
Column() {
Text(item + '')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.rotate({
z: 1,
angle: 2
})
}
.height(80)
.backgroundColor('#ffffff')
.visibility(item === this.dragData ? Visibility.Hidden : Visibility.Visible)
.onDragStart((event?: DragEvent, extraParams?: string) => {
this.dragData = item
})
.onDragEnd((event?: DragEvent, extraParams?: string) => {
this.dragData = null
})
.onDrop((event?: DragEvent, extraParams?: string) => {
})
})
}
.columnsTemplate('1fr 1fr')
.columnsGap(12)
.rowsGap(12)
.padding(12)
.backgroundColor(Color.Orange)
}
}
}
方案2:自定义拖拽预览图
使用 onDragStart 返回自定义的拖拽预览,避免变换影响:
@ComponentV2
export struct Drag {
@Local dataList: Array<number> = []
@Local dragData: number | null = null
aboutToAppear(): void {
for (let index = 0; index < 10; index++) {
this.dataList.push(index)
}
}
@Builder
dragPreview(item: number) {
Column() {
Text(item + '')
}
.width(80)
.height(80)
.backgroundColor('#ffffff')
.justifyContent(FlexAlign.Center)
.rotate({
z: 1,
angle: 2
})
}
build() {
Column() {
Text(this.dragData + '')
Grid() {
ForEach(this.dataList, (item: number) => {
GridItem() {
Text(item + '')
}
.height(80)
.backgroundColor('#ffffff')
.visibility(item === this.dragData ? Visibility.Hidden : Visibility.Visible)
.rotate({
z: 1,
angle: 2
})
.onDragStart((event?: DragEvent, extraParams?: string) => {
this.dragData = item
// 返回自定义拖拽预览
return this.dragPreview(item)
})
.onDragEnd((event?: DragEvent, extraParams?: string) => {
this.dragData = null
})
.onDrop((event?: DragEvent, extraParams?: string) => {
})
})
}
.columnsTemplate('1fr 1fr')
.columnsGap(12)
.rowsGap(12)
.padding(12)
.backgroundColor(Color.Orange)
}
}
}
方案3:使用 dragPreview 属性(API 10+)
GridItem() {
Text(item + '')
}
.height(80)
.backgroundColor('#ffffff')
.visibility(item === this.dragData ? Visibility.Hidden : Visibility.Visible)
.rotate({
z: 1,
angle: 2
})
.dragPreview(this.dragPreview(item)) // 设置拖拽预览
.onDragStart((event?: DragEvent, extraParams?: string) => {
this.dragData = item
})
方案4:动态移除 rotate
在拖拽开始时移除旋转,拖拽结束后恢复:
@ComponentV2
export struct Drag {
@Local dataList: Array<number> = []
@Local dragData: number | null = null
@Local isDragging: boolean = false
// ...
build() {
Column() {
Text(this.dragData + '')
Grid() {
ForEach(this.dataList, (item: number) => {
GridItem() {
Text(item + '')
}
.height(80)
.backgroundColor('#ffffff')
.visibility(item === this.dragData ? Visibility.Hidden : Visibility.Visible)
.rotate({
z: 1,
// 拖拽时移除旋转
angle: this.isDragging ? 0 : 2
})
.onDragStart((event?: DragEvent, extraParams?: string) => {
this.isDragging = true
this.dragData = item
})
.onDragEnd((event?: DragEvent, extraParams?: string) => {
this.isDragging = false
this.dragData = null
})
.onDrop((event?: DragEvent, extraParams?: string) => {
})
})
}
// ...
}
}
}
更多关于HarmonyOS 鸿蒙Next中在使用拖拽onDragStart时,如果拖拽元素设置了rotate的角度angle会导致拖拽开始时发生位移,该怎么解决?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
感谢,方案1是可以的,但是方案3自定义缩率图好像不行,
mark
当拖拽元素设置rotate角度后,拖拽起始点坐标系计算会出现偏移。这是由于系统默认以元素原始位置作为拖拽起始点,而旋转后的视觉坐标与逻辑坐标存在差异。
使用DragEvent.setDragDisplayOffset方法手动校正坐标偏移:
.onDragStart((event?: DragEvent, extraParams?: string) => {
this.dragData = item
// 校正旋转导致的坐标偏移
event?.setDragDisplayOffset({
x: 0, // 横向偏移量(根据实际旋转角度调整)
y: 0 // 纵向偏移量(根据实际旋转角度调整)
})
})
试了一下,onDragStart不支持这个,onDragStart是一个通用的拖拽事件,它在计算拖拽起始点时,是基于元素的布局位置,而没有考虑到元素被rotate后的视觉位置。可以用自定义拖拽手势,参考这个:
/*
* Copyright (c) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { curves, display } from '@kit.ArkUI';
import { i18n } from '@kit.LocalizationKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
@Component
export struct JitterAnimation {
@StorageProp('currentBreakpoint') curBp: string = 'sm';
@State isFoldAble: boolean = false;
@State foldStatus: number = 2;
@State numbers: number[] = [1, 2, 3, 4, 5];
@State isShowDelete: boolean = false;
@State isEdit: boolean = false;
@State rotateZ: number = 0;
@State dragItem: number = -1;
@State offsetX: number = 0;
@State offsetY: number = 0;
@State isEnglish: boolean = false;
private dragRefOffSetX: number = 0;
private dragRefOffSetY: number = 0;
private FIX_VP_X: number = 92;
private FIX_VP_Y: number = 84;
private leftArr: number[] = [];
private rightArr: number[] = [];
private downArr: number[] = [];
aboutToAppear(): void {
try {
this.isFoldAble = display.isFoldable();
let foldStatus: display.FoldStatus = display.getFoldStatus();
if (foldStatus === 2) {
this.FIX_VP_X = 81;
} else if (foldStatus === 1) {
this.FIX_VP_X = 110;
}
if (this.isFoldAble) {
this.foldStatus = foldStatus;
let callback: Callback<number> = () => {
let data: display.FoldStatus = display.getFoldStatus();
this.foldStatus = data;
if (this.foldStatus === 2) {
this.FIX_VP_X = 81;
} else if (this.foldStatus === 1) {
this.FIX_VP_X = 110;
}
}
display.on('change', callback);
}
let systemLanguage = i18n.System.getSystemLanguage();
if (systemLanguage === 'en-Latn-US') {
this.isEnglish = true;
}
for (let i = 0; i < this.numbers.length; i++) {
if (this.numbers[i] % 4 === 1) {
this.leftArr.push(i);
} else if (this.numbers[i] % 4 === 0) {
this.rightArr.push(i);
}
}
let n = this.numbers.length % 4;
let m = Math.floor(this.numbers.length / 4);
let downNum = (m - 1) * 4 + n;
for (let j = 0; j < downNum; j++) {
this.downArr.push(j);
}
} catch (error) {
let err = error as BusinessError;
hilog.error(0x0000, 'JitterAnimation',
`getFoldStatus failed. code=${err.code}, message=${err.message}`);
}
}
itemMove(index: number, newIndex: number): void {
if (!this.isDraggable(newIndex)) {
return;
}
let tmp = this.numbers.splice(index, 1);
this.numbers.splice(newIndex, 0, tmp[0]);
}
left(index: number): void {
if (!this.isDraggable(index - 1)) {
return;
}
this.offsetX += this.FIX_VP_X;
this.dragRefOffSetX -= this.FIX_VP_X;
this.itemMove(index, index - 1);
}
right(index: number): void {
if (!this.isDraggable(index + 1)) {
return;
}
this.offsetX -= this.FIX_VP_X;
this.dragRefOffSetX += this.FIX_VP_X;
this.itemMove(index, index + 1);
}
down(index: number): void {
if (!this.isDraggable(index + 5)) {
return;
}
this.offsetY -= this.FIX_VP_Y;
this.dragRefOffSetY += this.FIX_VP_Y;
this.itemMove(index, index + 4);
}
up(index: number): void {
if (this.curBp === 'md') {
if (!this.isDraggable(index - 5)) {
return;
}
this.offsetY += this.FIX_VP_Y;
this.dragRefOffSetY -= this.FIX_VP_Y;
this.itemMove(index, index - 5);
} else {
if (!this.isDraggable(index - 4)) {
return;
}
this.offsetY += this.FIX_VP_Y;
this.dragRefOffSetY -= this.FIX_VP_Y;
this.itemMove(index, index - 4);
}
}
isDraggable(index: number): boolean {
return index >= 0;
}
// [Start stopJump_start]
private stopJump() {
this.getUIContext().animateTo({
delay: 0,
tempo: 5,
duration: 0,
curve: Curve.Smooth,
playMode: PlayMode.Normal,
iterations: 1
}, () => {
this.rotateZ = 0;
})
}
private jumpWithSpeed(speed: number) {
if (this.isEdit) {
this.rotateZ = -1;
this.getUIContext().animateTo({
delay: 0,
tempo: speed,
duration: 1000,
curve: Curve.Smooth,
playMode: PlayMode.Normal,
iterations: -1
}, () => {
this.rotateZ = 1;
})
} else {
this.stopJump();
}
}
changeIndex(index1: number, index2: number) {
this.numbers.splice(index2, 0, this.numbers.splice(index1, 1)[0]);
}
@Builder
NavDestinationTitle() {
Column() {
Text($r('app.string.details_page'))
.fontSize(20)
.lineHeight(42)
.fontWeight(700)
.width('100%')
.padding({ left: 12 })
.fontColor(Color.White)
.opacity(0.9)
}
.width('100%')
}
build() {
NavDestination() {
Scroll() {
Column() {
Grid() {
ForEach(this.numbers, (item: number) => {
GridItem() {
Stack({ alignContent: Alignment.TopEnd }) {
Column() {
Image($r(`app.media.space${item}`))
.width(44)
.height(44)
.draggable(false)
Image($r('app.media.space_bottom'))
.width(16)
.height(16)
.draggable(false)
}
.width('100%')
.height(73)
.justifyContent(FlexAlign.Center)
.borderRadius(10)
.backgroundColor('#F1F3F5')
.animation({ curve: Curve.Sharp, duration: 300 })
.onClick(() => {
return;
})
if (this.isEdit) {
Image($r('app.media.close'))
.width(20)
.height(20)
.objectFit(ImageFit.Contain)
.draggable(false)
.position({
x: this.isFoldAble && this.foldStatus === 2 ? 60 :
this.isFoldAble && this.foldStatus === 1 ? 86 : 70,
y: -8
})
.onClick(() => {
this.getUIContext().animateTo({ duration: 300 }, () => {
this.numbers = this.numbers.filter((element) => element !== item);
})
})
}
}
}
.rotate({
z: 1,
angle: 2,
// centerX: '50%',
// centerY: '50%'
})
.width('100%')
.zIndex(this.dragItem === item ? 1 : 0)
.translate(this.dragItem === item ? { x: this.offsetX, y: this.offsetY } : { x: 0, y: 0 })
// [StartExclude GridItem3_start]
// [Start gesture3_start]
.gesture(
GestureGroup(GestureMode.Sequence,
LongPressGesture({ repeat: true })
.onAction(() => {
if (!this.isEdit) {
this.isEdit = true;
this.jumpWithSpeed(5);
}
}),
PanGesture({ fingers: 1, direction: null, distance: 0 })
.onActionStart(() => {
this.dragItem = item;
this.dragRefOffSetX = 0;
this.dragRefOffSetY = 0;
})
.onActionUpdate((event: GestureEvent) => {
this.offsetX = event.offsetX - this.dragRefOffSetX;
this.offsetY = event.offsetY - this.dragRefOffSetY;
this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
let index = this.numbers.indexOf(this.dragItem);
if (this.curBp === 'md') {
if (this.offsetX >= this.FIX_VP_X / 2 && (this.offsetY <= 50 && this.offsetY >= -50) &&
![4].includes(index)) {
this.right(index);
this.stopJump();
this.jumpWithSpeed(5);
} else if (this.offsetX <= -this.FIX_VP_X / 2 &&
(this.offsetY <= 50 && this.offsetY >= -50)) {
this.left(index);
this.stopJump();
this.jumpWithSpeed(5);
}
} else {
if (this.offsetY >= this.FIX_VP_Y / 2 && (this.offsetX <= 44 && this.offsetX >= -44) &&
[...this.downArr].includes(index)) {
this.down(index);
this.stopJump();
this.jumpWithSpeed(5);
} else if (this.offsetY <= -this.FIX_VP_Y / 2 &&
(this.offsetX <= 44 && this.offsetX >= -44)) {
this.up(index);
this.stopJump();
this.jumpWithSpeed(5);
} else if (this.offsetX >= this.FIX_VP_X / 2 && (this.offsetY <= 50 && this.offsetY >= -50) &&
![...this.rightArr].includes(index)) {
this.right(index);
this.stopJump();
this.jumpWithSpeed(5);
} else if (this.offsetX <= -this.FIX_VP_Y / 2 &&
(this.offsetY <= 50 && this.offsetY >= -50) &&
![...this.leftArr].includes(index)) {
this.left(index);
this.stopJump();
this.jumpWithSpeed(5);
}
}
})
})
.onActionEnd(() => {
this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
this.dragItem = -1;
})
})
)
)
// [End gesture3_start]
// [EndExclude GridItem3_start]
}, (item: number) => item.toString())
}
.width('100%')
.height('100%')
.editMode(true)
.clip(false)
.scrollBar(BarState.Off)
.columnsTemplate(this.curBp === 'md' ? '1fr 1fr 1fr 1fr 1fr' : '1fr 1fr 1fr 1fr')
.columnsGap(12)
.rowsGap(12)
.margin({ top: 5 })
// [End GridItem3_start]
}
.width(this.curBp === 'md' ? '80%' : '100%')
.height('100%')
.padding({
left: 16,
right: 16,
top: 12,
bottom: 16
})
}
.scrollBar(BarState.Off)
.padding({ bottom: 50 })
.onClick(() => {
this.isEdit = false;
this.stopJump();
})
}
.title(this.NavDestinationTitle())
.backButtonIcon($r('app.media.back2'))
.backgroundImage($r('app.media.backgroundImage'))
.backgroundImageSize({ width: '100%', height: '100%' })
}
}
在HarmonyOS鸿蒙Next中,拖拽元素设置rotate角度导致onDragStart位移的问题,可通过调整旋转中心点解决。使用transform-origin属性固定元素的旋转基准点,例如设置为"center"。同时确保元素布局属性不影响拖拽初始位置,避免使用可能引发重排的样式。检查元素在旋转状态下的实际坐标与拖拽事件获取坐标是否一致,必要时通过调整元素位置或拖拽事件参数进行校准。
在HarmonyOS Next中,当拖拽元素设置了rotate角度时,拖拽起始位置会发生偏移,这是因为旋转后的坐标系与原始坐标系不一致导致的。
解决方案是在onDragStart回调中通过event参数获取正确的拖拽位置信息:
.onDragStart((event?: DragEvent, extraParams?: string) => {
this.dragData = item
if (event) {
// 获取拖拽起始位置
const dragX = event.getX()
const dragY = event.getY()
// 根据实际需求调整拖拽位置
}
})
或者使用transform属性替代rotate来实现旋转效果,因为transform不会影响拖拽坐标系:
.transform({
rotate: { z: 1, angle: 2 }
})
这样可以避免旋转导致的拖拽位置偏移问题。

