鸿蒙app开发·滑动手势事件
①非滚动容器滚动事件(gesture)
// 手势动作参数
private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down})
// private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Left | PanDirection.Right})
//
。。。。
//添加手势事件(非滚动容器使用)
.gesture(
PanGesture(this.panOption)
.onActionEnd((event: GestureEvent) => {
if(event.offsetY<0) {
this.nowIndex++
}else if(event.offsetY>0) {
this.nowIndex--
}
console.log('this is nowIndex:',this.nowIndex)
this.scroller.scrollToIndex(this.nowIndex,true)
}),
)
②滚动容器手势事件(parallelGesture):Scroll+ Swiper、List、Grid、WaterFlow、Tabs + Scroll
// 手势动作参数
private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down})
// private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Left | PanDirection.Right})
。。。
.parallelGesture(
PanGesture(this.panOption)
// .onActionStart((event?: GestureEvent) => {
// console.info('wy Pan start',event);
// })
// .onActionUpdate((event?: GestureEvent) => {
// if (event) {
// console.info('wy Pan event',event);
// }
// })
.onActionEnd((event: GestureEvent) => {
if (event.offsetY < 0) {
this.nowIndex++
} else if (event.offsetY > 0) {
this.nowIndex--
}
console.log('this is nowIndex:', this.nowIndex)
this.scroller.scrollToIndex(this.nowIndex, true)
})
)
③平移手势和缩放手势
@Entry
@Component
struct Index {
build() {
RelativeContainer() {
GestureDemo()
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.width('100%')
.height(300)
}
.height('100%')
.width('100%')
}
}
@Component
struct GestureDemo {
@State isScaling: boolean = false;
@State positionX: number = 0;
@State positionY: number = 0;
@State offsetX: number = 0;
@State offsetY: number = 0;
@State scaleValue: number = 1;
@State pinchValue: number = 1;
@State pinchX: number = 0;
@State pinchY: number = 0;
build() {
Column() {
Image($r('app.media.startIcon'))
.width('100%')
.height('100%')
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
.scale({
x: this.scaleValue,
y: this.scaleValue,
z: 0
})
.translate({
x: this.offsetX,
y: this.offsetY,
z: 0
})
.gesture(
// 并行识别手势(同时响应拖拽和缩放)
GestureGroup(GestureMode.Parallel,
/* 缩放手势处理 */
PinchGesture({fingers: 2})
// 手势更新回调
.onActionUpdate((event: GestureEvent) => {
if(event){
// 计算临时缩放值:当前基准值 × 手势缩放比例
let tmpScale = this.pinchValue * event.scale;
// 缩放范围限制:0.5倍最小,4倍最大(临时可缩小到0.5倍)
this.scaleValue = Math.min(Math.max(tmpScale, 0.5), 4)
}
})
.onActionEnd(() => {
// 手势结束后处理回弹逻辑
if (this.scaleValue < 1) {
// 当缩放小于1倍时,执行弹性动画回到原始尺寸
animateTo({
duration: 200, // 动画时长200ms
curve: Curve.EaseOut // 使用缓出曲线
}, () => {
this.scaleValue = 1; // 重置为1倍缩放
this.pinchValue = 1; // 更新基准值
})
} else {
// 当缩放大于等于1倍时,保持当前值作为新基准
this.pinchValue = this.scaleValue
}
this.isScaling = false; // 更新缩放状态标识
}),
/* 拖动手势处理 */
PanGesture()
.onActionStart((event: GestureEvent) => {
console.log('拖动手势开始') // 手势开始日志
})
// 拖拽更新时的边界控制
.onActionUpdate((event: GestureEvent) => {
if(event){
// 计算缩放后的实际图片尺寸(容器尺寸300×300)
const scaledWidth = 300 * this.scaleValue;
const scaledHeight = 300 * this.scaleValue;
// 计算最大允许偏移量(确保图片边缘不超过容器50%)
const maxOffsetX = (scaledWidth - 300) / 2; // 水平最大偏移
const maxOffsetY = (scaledHeight - 300) / 2; // 垂直最大偏移
// 计算临时偏移量(考虑缩放对拖动距离的影响)
let newOffsetX = this.positionX + event.offsetX * this.scaleValue;
let newOffsetY = this.positionY + event.offsetY * this.scaleValue;
// 应用边界限制(使用Math函数确保偏移在允许范围内)
this.offsetX = Math.max(-maxOffsetX, Math.min(newOffsetX, maxOffsetX));
this.offsetY = Math.max(-maxOffsetY, Math.min(newOffsetY, maxOffsetY));
}
})
.onActionEnd(() => {
// 手势结束后边界回弹检查
const scaledWidth = 300 * this.scaleValue;
const scaledHeight = 300 * this.scaleValue;
const maxOffsetX = (scaledWidth - 300) / 2;
const maxOffsetY = (scaledHeight - 300) / 2;
// 当偏移超过限制时执行回弹动画
if (Math.abs(this.offsetX) > maxOffsetX || Math.abs(this.offsetY) > maxOffsetY) {
animateTo({
duration: 200,
curve: Curve.EaseOut
}, () => {
// 水平方向回弹处理
this.offsetX = this.offsetX > maxOffsetX ? maxOffsetX :
this.offsetX < -maxOffsetX ? -maxOffsetX : this.offsetX;
// 垂直方向回弹处理
this.offsetY = this.offsetY > maxOffsetY ? maxOffsetY :
this.offsetY < -maxOffsetY ? -maxOffsetY : this.offsetY;
// 更新基准位置
this.positionX = this.offsetX;
this.positionY = this.offsetY;
})
} else {
// 未超限时直接更新基准位置
this.positionX = this.offsetX;
this.positionY = this.offsetY;
}
})
)
)
}
}
综合使用(缩放、跟手)
import { CommonConstants, LogUtils } from '@ohos/base';
import { ImageKnifeComponentV2, ImageKnifeOptionV2 } from '@ohos/imageknife';
import { AlbumTab } from '../models/TabItem';
import { fileUri } from '@kit.CoreFileKit';
import { matrix4, window } from '@kit.ArkUI';
import { AppUtil } from '@pura/harmony-utils';
const TAG = 'ScalableImageComponent';
@ComponentV2
export struct ScalableImageComponent {
@Local imageKnifeOption: ImageKnifeOptionV2 | undefined;
@Param thumbnailUrl: string = '';
@Param albumTabType: AlbumTab = AlbumTab.CAMERA_ALBUM;
@Param remoteUrl: string = '';
@Local isDisableSwipe: boolean = false;
@Local imageWidth: number = 0;
@Local imageHeight: number = 0;
@Local imageWHRatio: number = 0;
@Local componentWidth: number = 0;
@Local componentHeight: number = 0;
@Local isOverlay: boolean = false;
@Local curScale: number = 1;
@Local maxScale: number = 3;
@Local defaultScaleValue: number = 1;
@Local curOffsetY: number = 0;
@Local curOffsetX: number = 0;
@Local lastOffsetY: number = 0;
@Local lastOffsetX: number = 0;
@Local centerY: number = 0;
@Local centerX: number = 0;
@Local lastScale: number = 1;
@Local isArriveBoundary: boolean = false;
@Local center: [number, number] = [0, 0];
@Local matrix: matrix4.Matrix4Transit | undefined;
@Local maxOffsetX: number = 0;
@Local maxOffsetY: number = 0;
@Local minOffsetX: number = 0;
@Local minOffsetY: number = 0;
@Local displayImageWidth: number = 0;
@Local displayImageHeight: number = 0;
aboutToAppear(): void {
this.imageKnifeOption = ({
placeholderSrc: this.thumbnailUrl,
loadSrc: this.albumTabType === AlbumTab.CAMERA_ALBUM ? this.remoteUrl : fileUri.getUriFromPath(this.remoteUrl),
objectFit: ImageFit.Contain,
errorholderSrc: $r('app.media.ic_new_media_placeholder_dark_16_9'),
onLoadListener: {
onLoadStart: () => {
LogUtils.debug(TAG, "Load start: " + this.remoteUrl);
},
onLoadFailed: (err) => {
LogUtils.debug(TAG, "Load Failed Reason: " + err + ",fileBean:" + this.remoteUrl);
},
onLoadSuccess: (data, imageData) => {
this.imageWidth = imageData.imageWidth;
this.imageHeight = imageData.imageHeight;
this.imageWHRatio = this.imageWidth / this.imageHeight;
this.calculateDisplaySize();
LogUtils.debug(TAG, `Load Successful: ${this.imageWidth}x${this.imageHeight}, display: ${this.displayImageWidth}x${this.displayImageHeight}, fileBean: ${this.remoteUrl}`);
return data;
}
}
});
this.windowSize();
}
private calculateDisplaySize(): void {
if (!this.componentWidth || !this.componentHeight || !this.imageWidth || !this.imageHeight) {
return;
}
const imageRatio = this.imageWidth / this.imageHeight;
const containerRatio = this.componentWidth / this.componentHeight;
if (imageRatio > containerRatio) {
this.displayImageWidth = this.componentWidth;
this.displayImageHeight = this.componentWidth / imageRatio;
} else {
this.displayImageHeight = this.componentHeight;
this.displayImageWidth = this.componentHeight * imageRatio;
}
}
build() {
ImageKnifeComponentV2({
imageKnifeOption: this.imageKnifeOption
})
.width(CommonConstants.FULL_WIDTH)
.transform(this.matrix ? this.matrix.copy() : matrix4.identity())
.translate({ x: this.curOffsetX, y: this.curOffsetY })
.gesture(
GestureGroup(GestureMode.Parallel,
TapGesture({ count: 2 })
.onAction((event: GestureEvent) => {
this.resetToDefaultScale();
}),
PanGesture({ fingers: 1, distance: this.isDisableSwipe ? 3 : 50 })
.onActionUpdate((event: GestureEvent) => {
this.isDisableSwipe = this.panGestureUpdate(event);
})
.onActionEnd(() => {
this.gestureEnd();
}),
PinchGesture({ fingers: 2, distance: 1 })
.onActionStart((event: GestureEvent) => {
this.pinchGestureStart(event);
if (this.isOverlay) {
this.isOverlay = false;
}
})
.onActionUpdate((event: GestureEvent) => {
this.isDisableSwipe = this.pinchGestureUpdate(event);
})
.onActionEnd(() => {
this.pinchGestureEnd();
})
)
)
}
private resetToDefaultScale(): void {
animateTo({
duration: 300,
curve: Curve.EaseOut
}, () => {
this.curScale = this.defaultScaleValue;
this.curOffsetX = 0;
this.curOffsetY = 0;
this.lastOffsetX = 0;
this.lastOffsetY = 0;
this.centerX = 0.5;
this.centerY = 0.5;
this.pictureScaling(this.curScale);
this.evaluateOffsetRange();
});
}
panGestureUpdate(event: GestureEvent): boolean {
this.onScale(this.lastScale, event.offsetX, event.offsetY);
this.pictureBoundaryRestriction();
return this.isDisableSwipe;
}
onScale(scale: number, offX: number, offY: number): void {
if (this.curScale > this.defaultScaleValue) {
this.curOffsetY = this.lastOffsetY + offY;
this.curOffsetX = this.lastOffsetX + offX;
}
this.isArriveBoundary = false;
if (!this.isArriveBoundary && this.curScale <= this.defaultScaleValue) {
this.isDisableSwipe = false;
} else {
this.isDisableSwipe = true;
}
}
evaluateCenter(centerX: number, centerY: number): [number, number] {
let imgDisplayWidth = this.displayImageWidth * this.lastScale;
let imgDisplayHeight = this.displayImageHeight * this.lastScale;
let imgX = (this.componentWidth - imgDisplayWidth) / 2 + this.lastOffsetX;
let imgY = (this.componentHeight - imgDisplayHeight) / 2 + this.lastOffsetY;
let cX = Math.max((centerX - imgX) / imgDisplayWidth, 0);
let cY = Math.max((centerY - imgY) / imgDisplayHeight, 0);
return [cX, cY];
}
pinchGestureStart(event: GestureEvent): void {
this.center = this.evaluateCenter(event.pinchCenterX, event.pinchCenterY);
this.centerX = 1 - this.center[0];
this.centerY = 1 - this.center[1];
this.lastScale = this.curScale;
this.lastOffsetX = this.curOffsetX;
this.lastOffsetY = this.curOffsetY;
}
pictureScaling(scale: number): void {
this.matrix = matrix4.identity().scale({
x: scale,
y: scale
}).copy();
}
pinchGestureUpdate(event: GestureEvent): boolean {
let newScale = this.lastScale * event.scale;
newScale = Math.min(Math.max(newScale, this.defaultScaleValue), this.maxScale);
this.curScale = newScale;
if (newScale > this.defaultScaleValue) {
this.curOffsetY = this.lastOffsetY + (event.offsetY || 0);
this.curOffsetX = this.lastOffsetX + (event.offsetX || 0);
}
this.pictureScaling(this.curScale);
if (this.curScale > this.defaultScaleValue) {
this.isDisableSwipe = true;
}
this.evaluateOffsetRange();
this.pictureBoundaryRestriction();
return this.isDisableSwipe;
}
pinchGestureEnd(): void {
this.lastScale = this.curScale;
this.lastOffsetX = this.curOffsetX;
this.lastOffsetY = this.curOffsetY;
if (this.curScale < this.defaultScaleValue) {
animateTo({
duration: 200,
curve: Curve.EaseOut
}, () => {
this.curScale = this.defaultScaleValue;
this.curOffsetX = 0;
this.curOffsetY = 0;
this.lastOffsetX = 0;
this.lastOffsetY = 0;
this.pictureScaling(this.curScale);
});
} else {
this.evaluateOffsetRange();
this.pictureBoundaryRestriction();
}
}
gestureEnd(): void {
this.lastOffsetX = this.curOffsetX;
this.lastOffsetY = this.curOffsetY;
this.evaluateOffsetRange();
this.pictureBoundaryRestriction();
}
windowSize(): void {
window.getLastWindow(AppUtil.getContext()).then((window: window.Window) => {
try {
this.componentWidth = this.getUIContext().px2vp(window.getWindowProperties().windowRect.width);
this.componentHeight = this.getUIContext().px2vp(window.getWindowProperties().windowRect.height);
LogUtils.debug(TAG, `Window size: ${this.componentWidth}x${this.componentHeight}`);
this.calculateDisplaySize();
} catch (err) {
LogUtils.error(TAG, `GetWindowProperties failed. Cause code: ${err.code}, message: ${err.message}`);
}
}).catch((err: BusinessError) => {
LogUtils.error(TAG, `GetLastWindow failed. Cause code: ${err.code}, message: ${err.message}`);
});
}
evaluateOffsetRange(): void {
let scaledImageWidth = this.displayImageWidth * this.curScale;
let scaledImageHeight = this.displayImageHeight * this.curScale;
if (scaledImageWidth > this.componentWidth) {
let maxXOffset = (scaledImageWidth - this.componentWidth) / 2;
this.maxOffsetX = maxXOffset;
this.minOffsetX = -maxXOffset;
} else {
this.maxOffsetX = 0;
this.minOffsetX = 0;
}
if (scaledImageHeight > this.componentHeight) {
let maxYOffset = (scaledImageHeight - this.componentHeight) / 2;
this.maxOffsetY = maxYOffset;
this.minOffsetY = -maxYOffset;
} else {
this.maxOffsetY = 0;
this.minOffsetY = 0;
}
LogUtils.debug(TAG, `Scaled: ${scaledImageWidth}x${scaledImageHeight}, Offset range X:[${this.minOffsetX}, ${this.maxOffsetX}], Y:[${this.minOffsetY}, ${this.maxOffsetY}]`);
}
pictureBoundaryRestriction(): void {
if (this.curOffsetX > this.maxOffsetX) {
this.curOffsetX = this.maxOffsetX;
this.isDisableSwipe = false;
this.isArriveBoundary = true;
} else if (this.curOffsetX < this.minOffsetX) {
this.curOffsetX = this.minOffsetX;
this.isDisableSwipe = false;
this.isArriveBoundary = true;
}
if (this.curOffsetY > this.maxOffsetY) {
this.curOffsetY = this.maxOffsetY;
} else if (this.curOffsetY < this.minOffsetY) {
this.curOffsetY = this.minOffsetY;
}
let scaledImageWidth = this.displayImageWidth * this.curScale;
let scaledImageHeight = this.displayImageHeight * this.curScale;
if (scaledImageWidth <= this.componentWidth) {
this.curOffsetX = 0;
}
if (scaledImageHeight <= this.componentHeight) {
this.curOffsetY = 0;
}
}
}
浙公网安备 33010602011771号