
鸿蒙 5 手势交互实战:拖拽、缩放与长按事件处理
一、手势交互核心类
鸿蒙 5 提供丰富的手势处理类:
import ohos.multimodalinput.event.TouchEvent;
import ohos.agp.components.Component;
import ohos.agp.utils.Point;
import ohos.multimodalinput.event.TouchEvent.TouchType;
二、拖拽手势实现
基础拖拽实现
public class DraggableComponent extends Component {
private float lastTouchX;
private float lastTouchY;
public DraggableComponent(Context context) {
super(context);
setTouchEventListener(this::handleDrag);
}
private boolean handleDrag(Component component, TouchEvent event) {
Point point = event.getPointerPosition(0);
switch (event.getAction()) {
case TouchEvent.PRIMARY_POINT_DOWN:
lastTouchX = point.getX();
lastTouchY = point.getY();
return true;
case TouchEvent.POINT_MOVE:
float deltaX = point.getX() - lastTouchX;
float deltaY = point.getY() - lastTouchY;
// 更新组件位置
setPositionX(getPositionX() + deltaX);
setPositionY(getPositionY() + deltaY);
// 更新坐标
lastTouchX = point.getX();
lastTouchY = point.getY();
return true;
case TouchEvent.PRIMARY_POINT_UP:
// 拖拽结束处理
return true;
}
return false;
}
}
边界限制拖拽
// 在POINT_MOVE中添加边界检查
private void updatePosition(float deltaX, float deltaY) {
float newX = getPositionX() + deltaX;
float newY = getPositionY() + deltaY;
// 限制在父容器范围内
ComponentContainer parent = (ComponentContainer) getParent();
if (newX < 0) newX = 0;
if (newY < 0) newY = 0;
if (newX > parent.getWidth() - getWidth())
newX = parent.getWidth() - getWidth();
if (newY > parent.getHeight() - getHeight())
newY = parent.getHeight() - getHeight();
setPositionX(newX);
setPositionY(newY);
}
磁性吸附效果
// 在拖拽结束时添加吸附逻辑
private void applySnapEffect() {
ComponentContainer parent = (ComponentContainer) getParent();
int centerX = parent.getWidth() / 2;
int centerY = parent.getHeight() / 2;
int threshold = 100; // 吸附阈值100vp
// 检查是否靠近中心点
if (Math.abs(getPositionX() + getWidth()/2 - centerX) < threshold &&
Math.abs(getPositionY() + getHeight()/2 - centerY) < threshold) {
// 动画吸附到中心
AnimatorProperty anim = new AnimatorProperty();
anim.moveFromX(getPositionX()).moveToX(centerX - getWidth()/2);
anim.moveFromY(getPositionY()).moveToY(centerY - getHeight()/2);
anim.setDuration(300).setCurve(Animator.Curve.ELASTIC);
anim.start();
}
}
三、缩放手势实现
双指缩放基础
public class ZoomableComponent extends Component {
private float initDistance;
private float currentScale = 1.0f;
private Point pivotPoint = new Point();
public ZoomableComponent(Context context) {
super(context);
setTouchEventListener(this::handleZoom);
}
private boolean handleZoom(Component component, TouchEvent event) {
if (event.getPointerCount() < 2) return false;
switch (event.getAction()) {
case TouchEvent.OTHER_POINT_DOWN:
initDistance = calculateDistance(event);
pivotPoint = calculateCenterPoint(event);
return true;
case TouchEvent.POINT_MOVE:
if (event.getPointerCount() >= 2) {
float newDistance = calculateDistance(event);
float scaleFactor = newDistance / initDistance;
// 设置缩放中心点
setPivotX(pivotPoint.getX() - getPositionX());
setPivotY(pivotPoint.getY() - getPositionY());
// 应用缩放(限制最小0.5倍,最大5倍)
float targetScale = currentScale * scaleFactor;
targetScale = Math.max(0.5f, Math.min(targetScale, 5.0f));
setScaleX(targetScale);
setScaleY(targetScale);
return true;
}
break;
case TouchEvent.POINT_UP:
case TouchEvent.OTHER_POINT_UP:
currentScale = getScaleX(); // 保存当前缩放值
return true;
}
return false;
}
// 计算两指距离
private float calculateDistance(TouchEvent event) {
Point p1 = event.getPointerPosition(0);
Point p2 = event.getPointerPosition(1);
return (float) Math.hypot(p2.getX() - p1.getX(), p2.getY() - p1.getY());
}
// 计算两指中点
private Point calculateCenterPoint(TouchEvent event) {
Point p1 = event.getPointerPosition(0);
Point p2 = event.getPointerPosition(1);
return new Point((p1.getX() + p2.getX()) / 2,
(p1.getY() + p2.getY()) / 2);
}
}
双击缩放实现
private long lastClickTime;
private Point lastClickPoint;
@Override
public void onClick(Component component) {
long clickTime = System.currentTimeMillis();
if (clickTime - lastClickTime < 300) { // 双击间隔300ms
// 获取双击位置
float pivotX = lastClickPoint.getX() - getPositionX();
float pivotY = lastClickPoint.getY() - getPositionY();
// 设置缩放中心点为双击位置
setPivotX(pivotX);
setPivotY(pivotY);
// 执行缩放动画
if (currentScale > 1.5f) {
// 缩小
AnimatorProperty anim = new AnimatorProperty();
anim.scaleXFrom(currentScale).scaleXTo(1.0f);
anim.scaleYFrom(currentScale).scaleYTo(1.0f);
anim.setDuration(300).start();
currentScale = 1.0f;
} else {
// 放大
AnimatorProperty anim = new AnimatorProperty();
anim.scaleXFrom(currentScale).scaleXTo(2.0f);
anim.scaleYFrom(currentScale).scaleYTo(2.0f);
anim.setDuration(300).start();
currentScale = 2.0f;
}
}
lastClickTime = clickTime;
lastClickPoint = TouchEventHelper.getTouchPoint(event);
}
四、长按手势实现
基础长按检测
public class LongPressComponent extends Component {
private static final int LONG_PRESS_DURATION = 500; // 500ms
private Handler handler = new Handler(Looper.getMainLooper());
private Runnable longPressRunnable;
public LongPressComponent(Context context) {
super(context);
setTouchEventListener(this::handleLongPress);
}
private boolean handleLongPress(Component component, TouchEvent event) {
switch (event.getAction()) {
case TouchEvent.PRIMARY_POINT_DOWN:
// 启动长按检测
longPressRunnable = () -> {
onLongPressDetected();
};
handler.postDelayed(longPressRunnable, LONG_PRESS_DURATION);
return true;
case TouchEvent.PRIMARY_POINT_UP:
// 取消长按检测
handler.removeCallbacks(longPressRunnable);
return true;
case TouchEvent.POINT_CANCEL:
// 取消长按检测
handler.removeCallbacks(longPressRunnable);
return true;
}
return false;
}
protected void onLongPressDetected() {
// 长按触发逻辑
addHapticFeedback();
showSelectionEffect();
}
// 触觉反馈
private void addHapticFeedback() {
Vibrator vibrator = new Vibrator();
VibratorPattern pattern = new VibratorPattern(
new long[]{0, 50}, // 时间点
new int[]{100, 50} // 震动强度
);
vibrator.vibrate(pattern);
}
// 视觉反馈
private void showSelectionEffect() {
AnimatorProperty anim = new AnimatorProperty();
anim.scaleXFrom(1.0f).scaleXTo(0.95f);
anim.scaleYFrom(1.0f).scaleYTo(0.95f);
anim.alphaFrom(1.0f).alphaTo(0.8f);
anim.setDuration(150).setCurve(Animator.Curve.DECELERATE);
anim.start();
}
}
长按拖拽组合
private boolean isLongPressTriggered = false;
private boolean handleLongPressDrag(Component component, TouchEvent event) {
switch (event.getAction()) {
case TouchEvent.PRIMARY_POINT_DOWN:
// 启动长按计时
handler.postDelayed(longPressRunnable, LONG_PRESS_DURATION);
return true;
case TouchEvent.POINT_MOVE:
if (isLongPressTriggered) {
// 执行拖拽逻辑
float deltaX = event.getPointerPosition(0).getX() - lastTouchX;
float deltaY = event.getPointerPosition(0).getY() - lastTouchY;
updatePosition(deltaX, deltaY);
lastTouchX = event.getPointerPosition(0).getX();
lastTouchY = event.getPointerPosition(0).getY();
return true;
}
return true;
case TouchEvent.PRIMARY_POINT_UP:
if (isLongPressTriggered) {
isLongPressTriggered = false;
onDragEnd();
return true;
}
handler.removeCallbacks(longPressRunnable);
return true;
}
return false;
}
@Override
protected void onLongPressDetected() {
isLongPressTriggered = true;
// 记录初始位置
lastTouchX = TouchEventHelper.getTouchPoint(event).getX();
lastTouchY = TouchEventHelper.getTouchPoint(event).getY();
// 视觉反馈
showDragFeedback();
}
五、复合手势:拖拽+缩放+旋转
public class AdvancedGestureView extends Component {
private int gestureMode = MODE_NONE;
private static final int MODE_NONE = 0;
private static final int MODE_DRAG = 1;
private static final int MODE_ZOOM = 2;
private float lastRotation;
public AdvancedGestureView(Context context) {
super(context);
setTouchEventListener(this::handleMultiTouch);
}
private boolean handleMultiTouch(Component component, TouchEvent event) {
switch (event.getAction()) {
case TouchEvent.PRIMARY_POINT_DOWN:
gestureMode = MODE_DRAG;
// 记录初始位置...
return true;
case TouchEvent.OTHER_POINT_DOWN:
if (gestureMode == MODE_DRAG && event.getPointerCount() >= 2) {
gestureMode = MODE_ZOOM;
// 记录初始距离...
// 记录初始旋转角度...
lastRotation = calculateRotation(event);
}
return true;
case TouchEvent.POINT_MOVE:
if (gestureMode == MODE_DRAG) {
// 拖拽处理...
} else if (gestureMode == MODE_ZOOM && event.getPointerCount() >= 2) {
// 缩放处理...
// 旋转处理
float newRotation = calculateRotation(event);
float rotationDelta = newRotation - lastRotation;
lastRotation = newRotation;
// 应用旋转(以两指中心点为旋转中心)
float centerX = (event.getPointerPosition(0).getX() +
event.getPointerPosition(1).getX()) / 2;
float centerY = (event.getPointerPosition(0).getY() +
event.getPointerPosition(1).getY()) / 2;
setPivotX(centerX - getPositionX());
setPivotY(centerY - getPositionY());
setRotation(getRotation() + rotationDelta);
}
return true;
case TouchEvent.PRIMARY_POINT_UP:
case TouchEvent.OTHER_POINT_UP:
gestureMode = MODE_NONE;
return true;
}
return false;
}
// 计算两指旋转角度
private float calculateRotation(TouchEvent event) {
Point p1 = event.getPointerPosition(0);
Point p2 = event.getPointerPosition(1);
float deltaX = p2.getX() - p1.getX();
float deltaY = p2.getY() - p1.getY();
return (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
}
}
六、最佳实践与性能优化
-
手势优先级处理
private boolean handleComplexGestures(Component component, TouchEvent event) {
// 先检查缩放
if (event.getPointerCount() >= 2) {
handleZoom(event);
return true;
}// 再检查拖拽
if (isDragInProgress) {
handleDrag(event);
return true;
}// 最后处理点击
if (isTouchDown) {
handlePress(event);
return true;
}return false;
} -
手势冲突解决
private float touchSlop = ViewConfiguration.getTouchSlop();
private boolean shouldStartDrag(TouchEvent downEvent, TouchEvent moveEvent) {
float deltaX = moveEvent.getPointerPosition(0).getX() -
downEvent.getPointerPosition(0).getX();
float deltaY = moveEvent.getPointerPosition(0).getY() -
downEvent.getPointerPosition(0).getY();
// 超过阈值才触发拖拽
return Math.sqrt(deltaX * deltaX + deltaY * deltaY) > touchSlop;
}
3. 性能优化建议
// 1. 启用硬件加速
setLayerType(LAYER_TYPE_HARDWARE);
// 2. 避免手势处理中的对象创建
private Point reusablePoint = new Point();
private void updatePosition(TouchEvent event) {
reusablePoint.set(event.getPointerPosition(0));
// 使用可重用对象…
}
// 3. 批量操作减少重绘
component.postDraw(() -> {
// 在下一个帧循环中执行多个操作
setPositionX(newX);
setPositionY(newY);
setRotation(newRotation);
});
七、完整示例:图片查看器
public class ImageViewer extends Component implements Component.TouchEventListener {
// 手势模式常量
private static final int MODE_NONE = 0;
private static final int MODE_DRAG = 1;
private static final int MODE_ZOOM = 2;
// 手势状态
private int mode = MODE_NONE;
private float lastX, lastY;
private float startDistance;
private float startScale = 1.0f;
private float lastRotation;
// 组件状态
private float currentScale = 1.0f;
private float currentRotation = 0f;
public ImageViewer(Context context) {
super(context);
setTouchEventListener(this);
}
@Override
public boolean onTouchEvent(Component component, TouchEvent event) {
switch (event.getAction()) {
case TouchEvent.PRIMARY_POINT_DOWN:
if (mode == MODE_NONE) {
mode = MODE_DRAG;
lastX = event.getPointerPosition(0).getX();
lastY = event.getPointerPosition(0).getY();
}
return true;
case TouchEvent.OTHER_POINT_DOWN:
if (mode == MODE_DRAG && event.getPointerCount() == 2) {
mode = MODE_ZOOM;
startDistance = getDistance(event);
startScale = currentScale;
lastRotation = getRotation(event);
}
return true;
case TouchEvent.POINT_MOVE:
if (mode == MODE_DRAG) {
float deltaX = event.getPointerPosition(0).getX() - lastX;
float deltaY = event.getPointerPosition(0).getY() - lastY;
setPositionX(getPositionX() + deltaX);
setPositionY(getPositionY() + deltaY);
lastX = event.getPointerPosition(0).getX();
lastY = event.getPointerPosition(0).getY();
return true;
}
else if (mode == MODE_ZOOM && event.getPointerCount() >= 2) {
// 缩放
float newDistance = getDistance(event);
float scale = startScale * (newDistance / startDistance);
scale = Math.max(0.5f, Math.min(scale, 5.0f));
setScaleX(scale);
setScaleY(scale);
currentScale = scale;
// 旋转
float newRotation = getRotation(event);
setRotation(currentRotation + (newRotation - lastRotation));
return true;
}
break;
case TouchEvent.PRIMARY_POINT_UP:
case TouchEvent.OTHER_POINT_UP:
if (mode == MODE_ZOOM) {
currentRotation = getRotation();
}
mode = MODE_NONE;
return true;
case TouchEvent.POINT_CANCEL:
mode = MODE_NONE;
return true;
}
return false;
}
// 复位位置和缩放
public void resetView() {
AnimatorProperty anim = new AnimatorProperty();
anim.scaleXFrom(currentScale).scaleXTo(1.0f);
anim.scaleYFrom(currentScale).scaleYTo(1.0f);
anim.rotateFrom(currentRotation).rotateTo(0);
anim.moveToX(0).moveToY(0);
anim.setDuration(300).start();
currentScale = 1.0f;
currentRotation = 0f;
}
// 工具方法...
}
八、响应式手势设计技巧
自适应触控区域
// 根据设备类型调整最小触控区域
private void adjustTouchSlop() {
DeviceInfo deviceInfo = DeviceInfoUtils.getDeviceInfo(getContext());
if (deviceInfo.getDeviceType() == DeviceType.TV) {
touchSlop *= 1.5f; // 电视设备增大触控阈值
}
else if (deviceInfo.getDeviceType() == DeviceType.SMART_WATCH) {
touchSlop *= 0.8f; // 手表设备减小触控阈值
}
}
折叠屏适配
private void handleFoldableEvent(WindowSizeChangeEvent event) {
if (event.getFoldableState() == FoldableState.FOLDED) {
// 折叠状态简化手势
setScaleEnabled(false);
setRotationEnabled(false);
} else {
// 展开状态启用所有手势
setScaleEnabled(true);
setRotationEnabled(true);
}
}
总结
鸿蒙 5 的手势交互实现要点:
手势基础:
使用 setTouchEventListener 处理触控事件
区分 PRIMARY_POINT_DOWN、POINT_MOVE 等事件类型
核心手势:
拖拽:通过位置差更新组件坐标
缩放:计算两指距离变化控制缩放
长按:使用 Handler 实现延迟触发
高级技巧:
// 组合手势:模式切换
if (pointerCount == 1) mode = MODE_DRAG;
else if (pointerCount >= 2) mode = MODE_ZOOM;
// 视觉反馈
addHapticFeedback(); // 震动反馈
showVisualEffect(); // 视觉反馈
性能优化:
// 使用硬件加速
setLayerType(LAYER_TYPE_HARDWARE);
// 批量操作减少重绘
component.postDraw(() -> { /* 批量更新 */ });
设备适配:
// 折叠屏/大屏特殊处理
if (isFoldable) adjustForFoldable();
通过以上实现方案,您可以创建出流畅、自然的鸿蒙应用手势交互体验,满足用户在拖拽、缩放、长按等操作中的各种需求。
